Skip to content

Commit 2c7330c

Browse files
quiet-nodevictor-yanevnatanasow
authored
chore: cherry picked debug_traceTransaction (#2630)
* feat: enhance debug_traceTransaction to work with Opcode Logger (#2480) * feat: Enhance debug_traceTransaction to work with Opcode Logger Signed-off-by: Victor Yanev <[email protected]> * feat: Enhance debug_traceTransaction to work with Opcode Logger Signed-off-by: Victor Yanev <[email protected]> * chore: Remove unused imports and config variable in debug.spec.ts Signed-off-by: Victor Yanev <[email protected]> * chore: extract `getQueryParams` to helpers.ts Signed-off-by: Victor Yanev <[email protected]> * chore: fix validation Signed-off-by: nikolay <[email protected]> * chore: final touches Signed-off-by: Victor Yanev <[email protected]> * fix: error message in param validations Signed-off-by: Victor Yanev <[email protected]> * test: fix expected error message in server.spec.ts Signed-off-by: Victor Yanev <[email protected]> * fix: tests in debug.spec.ts Signed-off-by: Victor Yanev <[email protected]> * fix: remove unused import Signed-off-by: Victor Yanev <[email protected]> --------- Signed-off-by: Victor Yanev <[email protected]> Signed-off-by: nikolay <[email protected]> Co-authored-by: nikolay <[email protected]> Signed-off-by: Logan Nguyen <[email protected]> * fix: debug_traceTransaction not working when we don't pass tracerConfig (#2628) Signed-off-by: Victor Yanev <[email protected]> Signed-off-by: Logan Nguyen <[email protected]> --------- Signed-off-by: Victor Yanev <[email protected]> Signed-off-by: nikolay <[email protected]> Signed-off-by: Logan Nguyen <[email protected]> Co-authored-by: Victor Yanev <[email protected]> Co-authored-by: nikolay <[email protected]>
1 parent 072d2b6 commit 2c7330c

File tree

15 files changed

+585
-189
lines changed

15 files changed

+585
-189
lines changed

packages/relay/src/formatters.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ const formatContractResult = (cr: any) => {
208208
return null;
209209
};
210210

211+
const strip0x = (input: string): string => {
212+
return input.startsWith(EMPTY_HEX) ? input.substring(2) : input;
213+
};
214+
211215
const prepend0x = (input: string): string => {
212216
return input.startsWith(EMPTY_HEX) ? input : EMPTY_HEX + input;
213217
};
@@ -294,6 +298,7 @@ export {
294298
trimPrecedingZeros,
295299
weibarHexToTinyBarInt,
296300
stringToHex,
301+
strip0x,
297302
toHexString,
298303
isValidEthereumAddress,
299304
isHex,

packages/relay/src/lib/clients/mirrorNodeClient.ts

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*-
1+
/* -
22
*
33
* Hedera JSON RPC Relay
44
*
@@ -18,8 +18,8 @@
1818
*
1919
*/
2020

21-
import Axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
22-
import { MirrorNodeClientError } from './../errors/MirrorNodeClientError';
21+
import Axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
22+
import { MirrorNodeClientError } from '../errors/MirrorNodeClientError';
2323
import { Logger } from 'pino';
2424
import constants from './../constants';
2525
import { Histogram, Registry } from 'prom-client';
@@ -31,8 +31,9 @@ import { install as betterLookupInstall } from 'better-lookup';
3131
import { CacheService } from '../services/cacheService/cacheService';
3232
import { ethers } from 'ethers';
3333

34-
const http = require('http');
35-
const https = require('https');
34+
import http from 'http';
35+
import https from 'https';
36+
import { IOpcodesResponse } from './models/IOpcodesResponse';
3637

3738
type REQUEST_METHODS = 'GET' | 'POST';
3839

@@ -73,6 +74,7 @@ export class MirrorNodeClient {
7374
private static GET_CONTRACT_RESULTS_DETAILS_BY_ADDRESS_AND_TIMESTAMP_ENDPOINT = `contracts/${MirrorNodeClient.ADDRESS_PLACEHOLDER}/results/${MirrorNodeClient.TIMESTAMP_PLACEHOLDER}`;
7475
private static GET_CONTRACT_RESULTS_DETAILS_BY_CONTRACT_ID_ENDPOINT = `contracts/${MirrorNodeClient.CONTRACT_ID_PLACEHOLDER}/results/${MirrorNodeClient.TIMESTAMP_PLACEHOLDER}`;
7576
private static GET_CONTRACTS_RESULTS_ACTIONS = `contracts/results/${MirrorNodeClient.TRANSACTION_ID_PLACEHOLDER}/actions`;
77+
private static GET_CONTRACTS_RESULTS_OPCODES = `contracts/results/${MirrorNodeClient.TRANSACTION_ID_PLACEHOLDER}/opcodes`;
7678
private static GET_CONTRACT_RESULT_ENDPOINT = 'contracts/results/';
7779
private static GET_CONTRACT_RESULT_LOGS_ENDPOINT = 'contracts/results/logs';
7880
private static GET_CONTRACT_RESULT_LOGS_BY_ADDRESS_ENDPOINT = `contracts/${MirrorNodeClient.ADDRESS_PLACEHOLDER}/results/logs`;
@@ -103,6 +105,7 @@ export class MirrorNodeClient {
103105
[MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_ENDPOINT, [404]],
104106
[MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_BY_ADDRESS_ENDPOINT, [404]],
105107
[MirrorNodeClient.GET_CONTRACT_RESULTS_ENDPOINT, [404]],
108+
[MirrorNodeClient.GET_CONTRACTS_RESULTS_OPCODES, [404]],
106109
[MirrorNodeClient.GET_NETWORK_EXCHANGERATE_ENDPOINT, [404]],
107110
[MirrorNodeClient.GET_NETWORK_FEES_ENDPOINT, [404]],
108111
[MirrorNodeClient.GET_TOKENS_ENDPOINT, [404]],
@@ -149,9 +152,18 @@ export class MirrorNodeClient {
149152
*/
150153
private readonly register: Registry;
151154

152-
private mirrorResponseHistogram;
155+
/**
156+
* The histogram used for tracking the response time of the mirror node.
157+
* @private
158+
*/
159+
private readonly mirrorResponseHistogram: Histogram;
153160

161+
/**
162+
* The cache service used for caching responses.
163+
* @private
164+
*/
154165
private readonly cacheService: CacheService;
166+
155167
static readonly EVM_ADDRESS_REGEX: RegExp = /\/accounts\/([\d\.]+)/;
156168

157169
static mirrorNodeContractResultsPageMax = parseInt(process.env.MIRROR_NODE_CONTRACT_RESULTS_PG_MAX!) || 25;
@@ -304,26 +316,23 @@ export class MirrorNodeClient {
304316
return `${baseUrl}${MirrorNodeClient.API_V1_POST_FIX}`;
305317
}
306318

307-
private async request(
319+
private async request<T>(
308320
path: string,
309321
pathLabel: string,
310322
method: REQUEST_METHODS,
311323
data?: any,
312324
requestIdPrefix?: string,
313325
retries?: number,
314-
): Promise<any> {
326+
): Promise<T | null> {
315327
const start = Date.now();
316328
// extract request id from prefix and remove trailing ']' character
317329
const requestId =
318330
requestIdPrefix
319331
?.split(MirrorNodeClient.REQUEST_PREFIX_SEPARATOR)[1]
320332
.replace(MirrorNodeClient.REQUEST_PREFIX_TRAILING_BRACKET, MirrorNodeClient.EMPTY_STRING) ||
321333
MirrorNodeClient.EMPTY_STRING;
322-
let ms;
323334
const controller = new AbortController();
324335
try {
325-
let response;
326-
327336
const axiosRequestConfig: AxiosRequestConfig = {
328337
headers: {
329338
[MirrorNodeClient.REQUESTID_LABEL]: requestId,
@@ -336,18 +345,23 @@ export class MirrorNodeClient {
336345
axiosRequestConfig['axios-retry'] = { retries };
337346
}
338347

348+
let response: AxiosResponse<T, any>;
339349
if (method === MirrorNodeClient.HTTP_GET) {
340-
response = await this.restClient.get(path, axiosRequestConfig);
350+
if (pathLabel == MirrorNodeClient.GET_CONTRACTS_RESULTS_OPCODES) {
351+
response = await this.web3Client.get<T>(path, axiosRequestConfig);
352+
} else {
353+
response = await this.restClient.get<T>(path, axiosRequestConfig);
354+
}
341355
} else {
342-
response = await this.web3Client.post(path, data, axiosRequestConfig);
356+
response = await this.web3Client.post<T>(path, data, axiosRequestConfig);
343357
}
344358

345359
const ms = Date.now() - start;
346360
this.logger.debug(`${requestId} [${method}] ${path} ${response.status} ${ms} ms`);
347-
this.mirrorResponseHistogram.labels(pathLabel, response.status).observe(ms);
361+
this.mirrorResponseHistogram.labels(pathLabel, response.status?.toString()).observe(ms);
348362
return response.data;
349363
} catch (error: any) {
350-
ms = Date.now() - start;
364+
const ms = Date.now() - start;
351365
const effectiveStatusCode =
352366
error.response?.status ||
353367
MirrorNodeClientError.ErrorCodes[error.code] ||
@@ -363,15 +377,25 @@ export class MirrorNodeClient {
363377
return null;
364378
}
365379

366-
async get(path: string, pathLabel: string, requestIdPrefix?: string, retries?: number): Promise<any> {
367-
return this.request(path, pathLabel, 'GET', null, requestIdPrefix, retries);
380+
async get<T = any>(path: string, pathLabel: string, requestIdPrefix?: string, retries?: number): Promise<T | null> {
381+
return this.request<T>(path, pathLabel, 'GET', null, requestIdPrefix, retries);
368382
}
369383

370-
async post(path: string, data: any, pathLabel: string, requestIdPrefix?: string, retries?: number): Promise<any> {
384+
async post<T = any>(
385+
path: string,
386+
data: any,
387+
pathLabel: string,
388+
requestIdPrefix?: string,
389+
retries?: number,
390+
): Promise<T | null> {
371391
if (!data) data = {};
372-
return this.request(path, pathLabel, 'POST', data, requestIdPrefix, retries);
392+
return this.request<T>(path, pathLabel, 'POST', data, requestIdPrefix, retries);
373393
}
374394

395+
/**
396+
* @returns null if the error code is in the accepted error responses,
397+
* @throws MirrorNodeClientError if the error code is not in the accepted error responses.
398+
*/
375399
handleError(
376400
error: any,
377401
path: string,
@@ -383,7 +407,7 @@ export class MirrorNodeClient {
383407
const mirrorError = new MirrorNodeClientError(error, effectiveStatusCode);
384408
const acceptedErrorResponses = MirrorNodeClient.acceptedErrorStatusesResponsePerRequestPathMap.get(pathLabel);
385409

386-
if (error.response && acceptedErrorResponses && acceptedErrorResponses.indexOf(effectiveStatusCode) !== -1) {
410+
if (error.response && acceptedErrorResponses?.includes(effectiveStatusCode)) {
387411
this.logger.debug(`${requestIdPrefix} [${method}] ${path} ${effectiveStatusCode} status`);
388412
return null;
389413
}
@@ -699,7 +723,7 @@ export class MirrorNodeClient {
699723
* the mirror node DB and `transaction_index` or `block_number` is returned as `undefined`. A single re-fetch is sufficient to
700724
* resolve this problem.
701725
* @param transactionIdOrHash
702-
* @param requestId
726+
* @param requestIdPrefix
703727
*/
704728
public async getContractResultWithRetry(transactionIdOrHash: string, requestIdPrefix?: string) {
705729
const contractResult = await this.getContractResult(transactionIdOrHash, requestIdPrefix);
@@ -745,6 +769,19 @@ export class MirrorNodeClient {
745769
);
746770
}
747771

772+
public async getContractsResultsOpcodes(
773+
transactionIdOrHash: string,
774+
requestIdPrefix?: string,
775+
params?: { memory?: boolean; stack?: boolean; storage?: boolean },
776+
): Promise<IOpcodesResponse | null> {
777+
const queryParams = params ? this.getQueryParams(params) : '';
778+
return this.get<IOpcodesResponse>(
779+
`${this.getContractResultsOpcodesByTransactionIdPath(transactionIdOrHash)}${queryParams}`,
780+
MirrorNodeClient.GET_CONTRACTS_RESULTS_OPCODES,
781+
requestIdPrefix,
782+
);
783+
}
784+
748785
public async getContractResultsByAddress(
749786
contractIdOrAddress: string,
750787
contractResultsParams?: IContractResultsParams,
@@ -928,6 +965,13 @@ export class MirrorNodeClient {
928965
);
929966
}
930967

968+
private getContractResultsOpcodesByTransactionIdPath(transactionIdOrHash: string) {
969+
return MirrorNodeClient.GET_CONTRACTS_RESULTS_OPCODES.replace(
970+
MirrorNodeClient.TRANSACTION_ID_PLACEHOLDER,
971+
transactionIdOrHash,
972+
);
973+
}
974+
931975
public async getTokenById(tokenId: string, requestIdPrefix?: string, retries?: number) {
932976
return this.get(
933977
`${MirrorNodeClient.GET_TOKENS_ENDPOINT}/${tokenId}`,
@@ -1009,7 +1053,6 @@ export class MirrorNodeClient {
10091053
* Check if transaction fail is because of contract revert and try to fetch and log the reason.
10101054
*
10111055
* @param e
1012-
* @param requestId
10131056
* @param requestIdPrefix
10141057
*/
10151058
public async getContractRevertReasonFromTransaction(e: any, requestIdPrefix: string): Promise<any | undefined> {
@@ -1087,6 +1130,7 @@ export class MirrorNodeClient {
10871130
* @param searchableTypes the types to search for
10881131
* @param callerName calling method name
10891132
* @param requestIdPrefix the request id prefix message
1133+
* @param retries the number of retries
10901134
* @returns entity object or null if not found
10911135
*/
10921136
public async resolveEntityType(
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/* -
2+
*
3+
* Hedera JSON RPC Relay
4+
*
5+
* Copyright (C) 2024 Hedera Hashgraph, LLC
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
*/
20+
21+
export interface IOpcode {
22+
pc?: number;
23+
op?: string;
24+
gas?: number;
25+
gas_cost?: number;
26+
depth?: number;
27+
stack?: string[];
28+
memory?: string[];
29+
storage?: { [key: string]: string };
30+
reason?: string | null;
31+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/* -
2+
*
3+
* Hedera JSON RPC Relay
4+
*
5+
* Copyright (C) 2024 Hedera Hashgraph, LLC
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
*/
20+
21+
import { IOpcode } from './IOpcode';
22+
23+
export interface IOpcodesResponse {
24+
address?: string;
25+
contract_id?: string;
26+
gas?: number;
27+
failed?: boolean;
28+
return_value?: string;
29+
opcodes?: IOpcode[];
30+
}

packages/relay/src/lib/services/debugService/IDebugService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import type { TracerType } from '../../constants';
2222

2323
export interface IDebugService {
2424
debug_traceTransaction: (
25-
transactionHash: string,
25+
transactionIdOrHash: string,
2626
tracer: TracerType,
2727
tracerConfig: object,
2828
requestIdPrefix?: string,

0 commit comments

Comments
 (0)