Skip to content

Commit c91ef46

Browse files
refactor: refactored debug_getRawReceipts to not reuse eth_getBlockReceipts functionality (#4892)
Signed-off-by: Bartosz Solka <bartosz.solka@blockydevs.com>
1 parent 26d47da commit c91ef46

File tree

16 files changed

+470
-327
lines changed

16 files changed

+470
-327
lines changed

docs/openrpc.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,10 +1413,10 @@
14131413
"description": "![](https://raw.githubusercontent.com/hiero-ledger/hiero-json-rpc-relay/main/docs/images/http_label.png)",
14141414
"params": [
14151415
{
1416-
"name": "Block",
1416+
"name": "block",
14171417
"required": true,
14181418
"schema": {
1419-
"$ref": "#/components/schemas/BlockNumberOrTag"
1419+
"$ref": "#/components/schemas/BlockNumberOrTagOrHash"
14201420
}
14211421
}
14221422
],

packages/relay/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export interface Debug {
3535

3636
traceBlockByHash(blockHash: string, tracerObject: BlockTracerConfig, requestDetails: RequestDetails): Promise<any>;
3737

38-
getRawReceipts(blockHashOrNumber: string, requestDetails: RequestDetails): Promise<string[] | null>;
38+
getRawReceipts(blockHashOrNumber: string, requestDetails: RequestDetails): Promise<string[]>;
3939
}
4040

4141
export interface Web3 {

packages/relay/src/lib/debug.ts

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services'
44
import type { Logger } from 'pino';
55

66
import { decodeErrorMessage, mapKeysAndValues, numberTo0x, prepend0x, strip0x, tinybarsToWeibars } from '../formatters';
7-
import { type Debug, Eth } from '../index';
7+
import { type Debug } from '../index';
88
import { Utils } from '../utils';
99
import { MirrorNodeClient } from './clients';
1010
import type { ICacheClient } from './clients/cache/ICacheClient';
@@ -13,8 +13,7 @@ import { IOpcodesResponse } from './clients/models/IOpcodesResponse';
1313
import constants, { CallType, TracerType } from './constants';
1414
import { cache, RPC_LAYOUT, rpcMethod, rpcParamLayoutConfig } from './decorators';
1515
import { predefined } from './errors/JsonRpcError';
16-
import { encodeReceiptToHex } from './receiptSerialization';
17-
import { CommonService } from './services';
16+
import { BlockService, CommonService, IBlockService } from './services';
1817
import {
1918
BlockTracerConfig,
2019
CallTracerResult,
@@ -67,10 +66,10 @@ export class DebugImpl implements Debug {
6766
private readonly cacheService: ICacheClient;
6867

6968
/**
70-
* The Eth implementation used for handling Ethereum-specific JSON-RPC requests.
69+
* The Block Service implementation that takes care of all block API operations.
7170
* @private
7271
*/
73-
private readonly eth: Eth;
72+
private readonly blockService: IBlockService;
7473

7574
/**
7675
* Creates an instance of DebugImpl.
@@ -79,14 +78,14 @@ export class DebugImpl implements Debug {
7978
* @param {MirrorNodeClient} mirrorNodeClient - The client for interacting with the mirror node.
8079
* @param {Logger} logger - The logger used for logging output from this class.
8180
* @param {ICacheClient} cacheService - Service for managing cached data.
82-
* @param {Eth} eth - The Eth implementation used for handling Ethereum-specific JSON-RPC requests.
81+
* @param {string} chainId - The chain identifier for the current blockchain environment.
8382
*/
84-
constructor(mirrorNodeClient: MirrorNodeClient, logger: Logger, cacheService: ICacheClient, eth: Eth) {
83+
constructor(mirrorNodeClient: MirrorNodeClient, logger: Logger, cacheService: ICacheClient, chainId: string) {
8584
this.logger = logger;
8685
this.common = new CommonService(mirrorNodeClient, logger, cacheService);
8786
this.mirrorNodeClient = mirrorNodeClient;
8887
this.cacheService = cacheService;
89-
this.eth = eth;
88+
this.blockService = new BlockService(cacheService, chainId, this.common, mirrorNodeClient, logger);
9089
}
9190

9291
/**
@@ -268,7 +267,7 @@ export class DebugImpl implements Debug {
268267
* @rpcMethod Exposed as debug_getRawReceipts RPC endpoint
269268
* @rpcParamValidationRules Applies JSON-RPC parameter validation according to the API specification
270269
*
271-
* @param {string} blockNumber - The number of the block to retrieve.
270+
* @param {string} blockHashOrNumber - The block hash or block number.
272271
* @param {RequestDetails} requestDetails - The request details for logging and tracking.
273272
* @throws {Error} Throws an error if the debug API is not enabled or if an exception occurs.
274273
* @returns {Promise<string[] | null>} A Promise that resolves to an array of EIP-2718 binary-encoded receipts or null if block not found.
@@ -279,16 +278,14 @@ export class DebugImpl implements Debug {
279278
*/
280279
@rpcMethod
281280
@rpcParamValidationRules({
282-
0: { type: 'blockNumber', required: true },
281+
0: { type: ['blockNumber', 'blockHash'], required: true },
283282
})
284-
@cache()
285-
async getRawReceipts(blockNumber: string, requestDetails: RequestDetails): Promise<string[] | null> {
283+
@cache({
284+
skipParams: [{ index: '0', value: constants.NON_CACHABLE_BLOCK_PARAMS }],
285+
})
286+
async getRawReceipts(blockHashOrNumber: string, requestDetails: RequestDetails): Promise<string[]> {
286287
DebugImpl.requireDebugAPIEnabled();
287-
const receipts = await this.eth.getBlockReceipts(blockNumber, requestDetails);
288-
if (!receipts) {
289-
return null;
290-
}
291-
return receipts.map((receipt) => encodeReceiptToHex(receipt));
288+
return await this.blockService.getRawReceipts(blockHashOrNumber, requestDetails);
292289
}
293290

294291
/**

packages/relay/src/lib/factories/transactionReceiptFactory.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { LogsBloomUtils } from '../../logsBloomUtils';
55
import constants from '../constants';
66
import { Log } from '../model';
77
import { ITransactionReceipt } from '../types';
8+
import { IReceiptRlpInput } from '../types/IReceiptRlpInput';
89

910
/**
1011
* Parameters specific to creating a synthetic transaction receipt from logs
@@ -25,6 +26,12 @@ interface IRegularTransactionReceiptParams {
2526
to: string | null;
2627
}
2728

29+
interface IRegularTransactionReceiptRlpInputParams {
30+
logs: Log[];
31+
receiptResponse: any;
32+
blockGasUsedBeforeTransaction: number;
33+
}
34+
2835
/**
2936
* Factory for creating different types of transaction receipts
3037
*/
@@ -57,6 +64,31 @@ class TransactionReceiptFactory {
5764
};
5865
}
5966

67+
/**
68+
* Creates a minimal receipt payload for RLP-encoding of a synthetic transaction.
69+
*
70+
* Builds an `IReceiptRlpInput` from synthetic logs only, without resolving any
71+
* addresses or constructing a full `ITransactionReceipt`. The returned shape
72+
* contains the fields required for Yellow Paper receipt encoding, including a zero
73+
* cumulative gas used, zero gas used, a logs bloom computed from the first
74+
* synthetic log, default root and status values, the transaction index from
75+
* the first log, and a fallback type of `0x0`.
76+
*
77+
* @param syntheticLogs - Logs belonging to the synthetic transaction.
78+
* @returns Minimal receipt data suitable for RLP encoding.
79+
*/
80+
public static createSyntheticReceiptRlpInput(syntheticLogs: Log[]): IReceiptRlpInput {
81+
return {
82+
cumulativeGasUsed: constants.ZERO_HEX,
83+
logs: syntheticLogs,
84+
logsBloom: LogsBloomUtils.buildLogsBloom(syntheticLogs[0].address, syntheticLogs[0].topics),
85+
root: constants.DEFAULT_ROOT_HASH,
86+
status: constants.ONE_HEX,
87+
transactionIndex: syntheticLogs[0].transactionIndex,
88+
type: constants.ZERO_HEX, // fallback to 0x0 from HAPI transactions
89+
};
90+
}
91+
6092
/**
6193
* Creates a regular transaction receipt from mirror node contract result data
6294
*
@@ -127,6 +159,32 @@ class TransactionReceiptFactory {
127159
return receipt;
128160
}
129161

162+
/**
163+
* Creates a minimal receipt payload for RLP-encoding of a regular transaction.
164+
*
165+
* Builds an `IReceiptRlpInput` from mirror node contract result data and the
166+
* running cumulative gas used before this transaction. The returned shape
167+
* contains only the fields required for Yellow Paper receipt encoding, including the updated cumulative gas used,
168+
* logs and bloom, root and status, transaction index, and normalized type.
169+
* @param params - Parameters required to build the RLP input, including
170+
* contract result data, associated logs, and the cumulative gas used prior
171+
* to this transaction.
172+
* @returns Minimal receipt data suitable for RLP encoding.
173+
*/
174+
public static createReceiptRlpInput(params: IRegularTransactionReceiptRlpInputParams): IReceiptRlpInput {
175+
const { receiptResponse, logs, blockGasUsedBeforeTransaction } = params;
176+
177+
return {
178+
cumulativeGasUsed: numberTo0x(blockGasUsedBeforeTransaction + receiptResponse.gas_used),
179+
logs: logs,
180+
logsBloom: receiptResponse.bloom === constants.EMPTY_HEX ? constants.EMPTY_BLOOM : receiptResponse.bloom,
181+
root: receiptResponse.root || constants.DEFAULT_ROOT_HASH,
182+
status: receiptResponse.status,
183+
transactionIndex: numberTo0x(receiptResponse.transaction_index),
184+
type: nanOrNumberTo0x(receiptResponse.type),
185+
};
186+
}
187+
130188
/**
131189
* Helper method to determine if a receipt response includes a contract address
132190
*
@@ -149,4 +207,9 @@ class TransactionReceiptFactory {
149207
}
150208
}
151209

152-
export { ISyntheticTransactionReceiptParams, IRegularTransactionReceiptParams, TransactionReceiptFactory };
210+
export {
211+
ISyntheticTransactionReceiptParams,
212+
IRegularTransactionReceiptParams,
213+
IRegularTransactionReceiptRlpInputParams,
214+
TransactionReceiptFactory,
215+
};

packages/relay/src/lib/receiptSerialization.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,33 @@ import { bytesToInt, concatBytes, hexToBytes, intToBytes } from '@ethereumjs/uti
1616

1717
import { prepend0x, toHexString } from '../formatters';
1818
import constants from './constants';
19-
import type { ITransactionReceipt } from './types';
19+
import type { Log } from './model';
20+
import { IReceiptRlpInput } from './types/IReceiptRlpInput';
2021

21-
// Log shape used for encoding: address, topics[], data (per Yellow Paper log structure)
22-
function encodeLogsForReceipt(logs: ITransactionReceipt['logs']): [Uint8Array, Uint8Array[], Uint8Array][] {
22+
/**
23+
* Converts receipt logs into the RLP encoded log structure.
24+
*
25+
* Each log becomes a 3-tuple [address, topics[], data] per the Yellow Paper
26+
* (address and data as bytes; topics as array of 32-byte topic hashes).
27+
*
28+
* @param logs - The logs array from the transaction receipt (see {@link Log}).
29+
* @returns Array of [address, topics, data] as Uint8Arrays for RLP encoding.
30+
*/
31+
function encodeLogsForReceipt(logs: Log[]): [Uint8Array, Uint8Array[], Uint8Array][] {
2332
return logs.map((log) => [hexToBytes(log.address), log.topics.map((t) => hexToBytes(t)), hexToBytes(log.data)]);
2433
}
2534

2635
/**
27-
* Encodes a single receipt to EIP-2718 binary form (hex string).
36+
* Encodes a single transaction receipt to EIP-2718 binary form.
37+
*
38+
* Produces the RLP-encoded 4-tuple (receipt_root_or_status, cumulative_gas_used,
39+
* logs_bloom, logs) per the Ethereum Yellow Paper. For typed transactions (type !== 0),
40+
* the output is the single-byte type prefix followed by that RLP payload (EIP-2718).
41+
*
42+
* @param receipt - The transaction receipt to encode (see {@link ITransactionReceipt}).
43+
* @returns Hex string (0x-prefixed) of the encoded receipt, suitable for receipts root hashing.
2844
*/
29-
export function encodeReceiptToHex(receipt: ITransactionReceipt): string {
45+
export function encodeReceiptToHex(receipt: IReceiptRlpInput): string {
3046
const txType = receipt.type !== null ? bytesToInt(hexToBytes(receipt.type)) : 0;
3147

3248
// First field: receipt root or status (post-Byzantium)

packages/relay/src/lib/relay.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ export class Relay {
375375
this.txpoolImpl = new TxPoolImpl(transactionPoolService);
376376

377377
// Create Debug and Admin implementations
378-
this.debugImpl = new DebugImpl(this.mirrorNodeClient, this.logger, this.cacheService, this.ethImpl);
378+
this.debugImpl = new DebugImpl(this.mirrorNodeClient, this.logger, this.cacheService, chainId);
379379
this.adminImpl = new AdminImpl(this.cacheService);
380380

381381
// Create HBAR spending plan config service

packages/relay/src/lib/services/ethService/blockService/BlockService.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
import { Logger } from 'pino';
44

55
import { numberTo0x } from '../../../../formatters';
6+
import { ICacheClient } from '../../../clients/cache/ICacheClient';
67
import { MirrorNodeClient } from '../../../clients/mirrorNodeClient';
78
import constants from '../../../constants';
89
import { Block } from '../../../model';
910
import { ITransactionReceipt, MirrorNodeBlock, RequestDetails } from '../../../types';
1011
import { IBlockService, ICommonService } from '../../index';
1112
import { WorkersPool } from '../../workersService/WorkersPool';
12-
import { ICacheClient } from '../../../clients/cache/ICacheClient';
1313

1414
export class BlockService implements IBlockService {
1515
/**
@@ -115,6 +115,25 @@ export class BlockService implements IBlockService {
115115
);
116116
}
117117

118+
/**
119+
* Gets the raw transaction receipts for a block by block hash or block number.
120+
*
121+
* @param {string} blockHashOrBlockNumber The block hash or block number
122+
* @param {RequestDetails} requestDetails The request details for logging and tracking
123+
* @returns {Promise<string[]>} Array of raw block receipts for the block
124+
*/
125+
public async getRawReceipts(blockHashOrBlockNumber: string, requestDetails: RequestDetails): Promise<string[]> {
126+
return WorkersPool.run(
127+
{
128+
type: 'getRawReceipts',
129+
blockHashOrBlockNumber,
130+
requestDetails,
131+
},
132+
this.mirrorNodeClient,
133+
this.cacheService,
134+
);
135+
}
136+
118137
/**
119138
* Gets the number of transaction in a block by its block hash.
120139
*

packages/relay/src/lib/services/ethService/blockService/IBlockService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface IBlockService {
1313
getBlockTransactionCountByHash: (hash: string, requestDetails: RequestDetails) => Promise<string | null>;
1414
getBlockTransactionCountByNumber: (blockNum: string, requestDetails: RequestDetails) => Promise<string | null>;
1515
getBlockReceipts: (blockHash: string, requestDetails: RequestDetails) => Promise<ITransactionReceipt[] | null>;
16+
getRawReceipts: (blockHashOrBlockNumber: string, requestDetails: RequestDetails) => Promise<string[]>;
1617
getUncleByBlockHashAndIndex: (blockHash: string, index: string) => null;
1718
getUncleByBlockNumberAndIndex: (blockNumOrTag: string, index: string) => null;
1819
getUncleCountByBlockHash: (blockHash: string) => string;

0 commit comments

Comments
 (0)