Skip to content

Commit f59446f

Browse files
chore: Improves performance and adds synthetic transaction support to getBlockReceipts (#3758)
Signed-off-by: Konstantina Blazhukova <[email protected]>
1 parent 6715666 commit f59446f

File tree

8 files changed

+168
-56
lines changed

8 files changed

+168
-56
lines changed

packages/relay/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"test": "nyc ts-mocha --recursive './tests/**/*.spec.ts' './tests/**/**/*.spec.ts' --exit",
3030
"test-eth": "nyc ts-mocha --recursive './tests/lib/eth/**/*.spec.ts' --exit",
3131
"test:eth-get-block-by-number": "nyc ts-mocha --recursive './tests/**/*.spec.ts' './tests/**/**/*.spec.ts' -g '@ethGetBlockByNumber' --exit",
32+
"test:eth-get-block-receipts": "nyc ts-mocha --recursive './tests/**/*.spec.ts' './tests/**/**/*.spec.ts' -g '@ethGetBlockReceipts' --exit",
3233
"test:eth-get-block-by-hash": "nyc ts-mocha --recursive './tests/**/*.spec.ts' './tests/**/**/*.spec.ts' -g '@ethGetBlockByHash' --exit",
3334
"test:eth-get-block-transaction-count-by-number": "nyc ts-mocha --recursive './tests/**/*.spec.ts' './tests/**/**/*.spec.ts' -g '@ethGetBlockTransactionCountByNumber' --exit",
3435
"test:eth-get-block-transaction-count-by-hash": "nyc ts-mocha --recursive './tests/**/*.spec.ts' './tests/**/**/*.spec.ts' -g '@ethGetBlockTransactionCountByHash' --exit",

packages/relay/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
IGetLogsParams,
1212
INewFilterParams,
1313
ITracerConfig,
14+
ITransactionReceipt,
1415
RequestDetails,
1516
} from './lib/types';
1617

@@ -70,7 +71,7 @@ export interface Eth {
7071

7172
getBalance(account: string, blockNumber: string | null, requestDetails: RequestDetails): Promise<string>;
7273

73-
getBlockReceipts(blockHashOrNumber: string, requestDetails: RequestDetails): Promise<Receipt[]>;
74+
getBlockReceipts(blockHashOrNumber: string, requestDetails: RequestDetails): Promise<ITransactionReceipt[]>;
7475

7576
getBlockByHash(hash: string, showDetails: boolean, requestDetails: RequestDetails): Promise<Block | null>;
7677

packages/relay/src/lib/eth.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,14 @@ import { FeeService } from './services/ethService/feeService/FeeService';
2626
import { IFeeService } from './services/ethService/feeService/IFeeService';
2727
import { ITransactionService } from './services/ethService/transactionService/ITransactionService';
2828
import HAPIService from './services/hapiService/hapiService';
29-
import { IContractCallRequest, IFeeHistory, IGetLogsParams, INewFilterParams, RequestDetails } from './types';
29+
import {
30+
IContractCallRequest,
31+
IFeeHistory,
32+
IGetLogsParams,
33+
INewFilterParams,
34+
ITransactionReceipt,
35+
RequestDetails,
36+
} from './types';
3037
import { ParamType } from './types/validation';
3138

3239
/**
@@ -125,7 +132,7 @@ export class EthImpl implements Eth {
125132
this.feeService = new FeeService(mirrorNodeClient, this.common, logger);
126133
this.contractService = new ContractService(cacheService, this.common, hapiService, logger, mirrorNodeClient);
127134
this.accountService = new AccountService(cacheService, this.common, logger, mirrorNodeClient);
128-
this.blockService = new BlockService(chain, this.common, mirrorNodeClient, logger);
135+
this.blockService = new BlockService(cacheService, chain, this.common, mirrorNodeClient, logger);
129136
this.eventEmitter = eventEmitter;
130137
this.transactionService = new TransactionService(
131138
cacheService,
@@ -1110,7 +1117,10 @@ export class EthImpl implements Eth {
11101117
@cache(CacheService.getInstance(CACHE_LEVEL.L1), {
11111118
skipParams: [{ index: '0', value: constants.NON_CACHABLE_BLOCK_PARAMS }],
11121119
})
1113-
public async getBlockReceipts(blockHashOrBlockNumber: string, requestDetails: RequestDetails): Promise<Receipt[]> {
1120+
public async getBlockReceipts(
1121+
blockHashOrBlockNumber: string,
1122+
requestDetails: RequestDetails,
1123+
): Promise<ITransactionReceipt[]> {
11141124
return await this.blockService.getBlockReceipts(blockHashOrBlockNumber, requestDetails);
11151125
}
11161126

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

Lines changed: 81 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,30 @@ import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services'
33
import _ from 'lodash';
44
import { Logger } from 'pino';
55

6-
import { nanOrNumberTo0x, nullableNumberTo0x, numberTo0x, toHash32 } from '../../../../formatters';
6+
import { nanOrNumberTo0x, numberTo0x } from '../../../../formatters';
77
import { IReceiptRootHash, ReceiptsRootUtils } from '../../../../receiptsRootUtils';
88
import { Utils } from '../../../../utils';
99
import { MirrorNodeClient } from '../../../clients/mirrorNodeClient';
1010
import constants from '../../../constants';
1111
import { predefined } from '../../../errors/JsonRpcError';
1212
import { BlockFactory } from '../../../factories/blockFactory';
1313
import { TransactionFactory } from '../../../factories/transactionFactory';
14-
import { Block, Log, Receipt, Transaction } from '../../../model';
15-
import { IContractResultsParams, MirrorNodeBlock, RequestDetails } from '../../../types';
14+
import {
15+
IRegularTransactionReceiptParams,
16+
TransactionReceiptFactory,
17+
} from '../../../factories/transactionReceiptFactory';
18+
import { Block, Log, Transaction } from '../../../model';
19+
import { IContractResultsParams, ITransactionReceipt, MirrorNodeBlock, RequestDetails } from '../../../types';
20+
import { CacheService } from '../../cacheService/cacheService';
1621
import { IBlockService, ICommonService } from '../../index';
1722
import { CommonService } from '../ethCommonService/CommonService';
18-
1923
export class BlockService implements IBlockService {
24+
/**
25+
* The cache service used for caching all responses.
26+
* @private
27+
*/
28+
private readonly cacheService: CacheService;
29+
2030
/**
2131
* The chain id.
2232
* @private
@@ -47,7 +57,14 @@ export class BlockService implements IBlockService {
4757
private readonly mirrorNodeClient: MirrorNodeClient;
4858

4959
/** Constructor */
50-
constructor(chain: string, common: ICommonService, mirrorNodeClient: MirrorNodeClient, logger: Logger) {
60+
constructor(
61+
cacheService: CacheService,
62+
chain: string,
63+
common: ICommonService,
64+
mirrorNodeClient: MirrorNodeClient,
65+
logger: Logger,
66+
) {
67+
this.cacheService = cacheService;
5168
this.chain = chain;
5269
this.common = common;
5370
this.mirrorNodeClient = mirrorNodeClient;
@@ -106,13 +123,28 @@ export class BlockService implements IBlockService {
106123
* @param {RequestDetails} requestDetails The request details for logging and tracking
107124
* @returns {Promise<Receipt[]>} Array of transaction receipts for the block
108125
*/
109-
public async getBlockReceipts(blockHashOrBlockNumber: string, requestDetails: RequestDetails): Promise<Receipt[]> {
126+
public async getBlockReceipts(
127+
blockHashOrBlockNumber: string,
128+
requestDetails: RequestDetails,
129+
): Promise<ITransactionReceipt[]> {
110130
const requestIdPrefix = requestDetails.formattedRequestId;
111131
if (this.logger.isLevelEnabled('trace')) {
112132
this.logger.trace(`${requestIdPrefix} getBlockReceipt(${JSON.stringify(blockHashOrBlockNumber)})`);
113133
}
114134

115135
const block = await this.common.getHistoricalBlockResponse(requestDetails, blockHashOrBlockNumber);
136+
137+
if (block == null) {
138+
throw predefined.RESOURCE_NOT_FOUND(`Block: ${blockHashOrBlockNumber}`);
139+
}
140+
141+
const blockNumber = block.number;
142+
const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_RECEIPTS}_${blockNumber}`;
143+
const cachedResponse = await this.cacheService.getAsync(cacheKey, constants.ETH_GET_BLOCK_RECEIPTS, requestDetails);
144+
if (cachedResponse) {
145+
return cachedResponse;
146+
}
147+
116148
const paramTimestamp: IContractResultsParams = {
117149
timestamp: [`lte:${block.timestamp.to}`, `gte:${block.timestamp.from}`],
118150
};
@@ -122,39 +154,56 @@ export class BlockService implements IBlockService {
122154
return [];
123155
}
124156

125-
const effectiveGas = await this.common.getCurrentGasPriceForBlock(block.hash, requestDetails);
157+
const receipts: ITransactionReceipt[] = [];
158+
const effectiveGas = numberTo0x(await this.common.getGasPriceInWeibars(block.timestamp.from.split('.')[0]));
126159

127160
const logs = await this.common.getLogsWithParams(null, paramTimestamp, requestDetails);
128-
contractResults.forEach((contractResult) => {
129-
contractResult.logs = logs.filter((log) => log.transactionHash === contractResult.hash);
130-
});
131161

132-
const receipts: Receipt[] = [];
162+
const logsByHash = new Map<string, Log[]>();
163+
for (const log of logs) {
164+
const existingLogs = logsByHash.get(log.transactionHash) || [];
165+
existingLogs.push(log);
166+
logsByHash.set(log.transactionHash, existingLogs);
167+
}
133168

134-
for (const contractResult of contractResults) {
135-
const from = await this.common.resolveEvmAddress(contractResult.from, requestDetails);
136-
const to = await this.common.resolveEvmAddress(contractResult.to, requestDetails);
137-
138-
const contractAddress = this.common.getContractAddressFromReceipt(contractResult);
139-
const receipt = {
140-
blockHash: toHash32(contractResult.block_hash),
141-
blockNumber: numberTo0x(contractResult.block_number),
142-
from: from,
143-
to: to,
144-
cumulativeGasUsed: numberTo0x(contractResult.block_gas_used),
145-
gasUsed: nanOrNumberTo0x(contractResult.gas_used),
146-
contractAddress: contractAddress,
169+
const receiptPromises = contractResults.map(async (contractResult) => {
170+
if (Utils.isRevertedDueToHederaSpecificValidation(contractResult)) {
171+
if (this.logger.isLevelEnabled('debug')) {
172+
this.logger.debug(
173+
`${requestIdPrefix} Transaction with hash ${contractResult.hash} is skipped due to hedera-specific validation failure (${contractResult.result})`,
174+
);
175+
}
176+
return null;
177+
}
178+
contractResult.logs = logsByHash.get(contractResult.hash) || [];
179+
const [from, to] = await Promise.all([
180+
this.common.resolveEvmAddress(contractResult.from, requestDetails),
181+
this.common.resolveEvmAddress(contractResult.to, requestDetails),
182+
]);
183+
const transactionReceiptParams: IRegularTransactionReceiptParams = {
184+
effectiveGas,
185+
from,
147186
logs: contractResult.logs,
148-
logsBloom: contractResult.bloom === constants.EMPTY_HEX ? constants.EMPTY_BLOOM : contractResult.bloom,
149-
transactionHash: toHash32(contractResult.hash),
150-
transactionIndex: numberTo0x(contractResult.transaction_index),
151-
effectiveGasPrice: effectiveGas,
152-
root: contractResult.root || constants.DEFAULT_ROOT_HASH,
153-
status: contractResult.status,
154-
type: nullableNumberTo0x(contractResult.type),
187+
receiptResponse: contractResult,
188+
to,
155189
};
190+
return TransactionReceiptFactory.createRegularReceipt(transactionReceiptParams) as ITransactionReceipt;
191+
});
192+
193+
const resolvedReceipts = await Promise.all(receiptPromises);
194+
receipts.push(...resolvedReceipts.filter(Boolean));
156195

157-
receipts.push(receipt);
196+
const regularTxHashes = new Set(contractResults.map((result) => result.hash));
197+
198+
// filtering out the synthetic tx hashes and creating the synthetic receipt
199+
for (const [txHash, logGroup] of logsByHash.entries()) {
200+
if (!regularTxHashes.has(txHash)) {
201+
const syntheticReceipt = TransactionReceiptFactory.createSyntheticReceipt({
202+
syntheticLogs: logGroup,
203+
gasPriceForTimestamp: effectiveGas,
204+
});
205+
receipts.push(syntheticReceipt as ITransactionReceipt);
206+
}
158207
}
159208

160209
return receipts;

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: Apache-2.0
22

3-
import { Block, Receipt } from '../../../model';
4-
import { RequestDetails } from '../../../types';
3+
import { Block } from '../../../model';
4+
import { ITransactionReceipt, RequestDetails } from '../../../types';
55

66
export interface IBlockService {
77
getBlockByNumber: (
@@ -12,7 +12,7 @@ export interface IBlockService {
1212
getBlockByHash: (hash: string, showDetails: boolean, requestDetails: RequestDetails) => Promise<Block | null>;
1313
getBlockTransactionCountByHash: (hash: string, requestDetails: RequestDetails) => Promise<string | null>;
1414
getBlockTransactionCountByNumber: (blockNum: string, requestDetails: RequestDetails) => Promise<string | null>;
15-
getBlockReceipts: (blockHash: string, requestDetails: RequestDetails) => Promise<Receipt[]>;
15+
getBlockReceipts: (blockHash: string, requestDetails: RequestDetails) => Promise<ITransactionReceipt[]>;
1616
getUncleByBlockHashAndIndex: (requestDetails: RequestDetails) => Promise<null>;
1717
getUncleByBlockNumberAndIndex: (requestDetails: RequestDetails) => Promise<null>;
1818
getUncleCountByBlockHash: (requestDetails: RequestDetails) => Promise<string>;

packages/relay/tests/assertions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export default class RelayAssertions {
2424
if (!checkMessage) {
2525
return err.code === error.code;
2626
}
27+
2728
return err.code === error.code && err.message === error.message;
2829
},
2930
);

0 commit comments

Comments
 (0)