Skip to content

fix: Aligns debug_traceTransaction validation with industry standards #3789

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 1 addition & 19 deletions docs/openrpc.json
Original file line number Diff line number Diff line change
Expand Up @@ -1064,29 +1064,11 @@
},
"description": "The hash of the transaction to trace."
},
{
"name": "tracer",
"required": false,
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/TracerType"
},
{
"$ref": "#/components/schemas/TracerConfig"
},
{
"$ref": "#/components/schemas/TracerConfigWrapper"
}
]
},
"description": "Specifies the type of tracer or configuration object for the tracer."
},
{
"name": "tracerConfig",
"required": false,
"schema": {
"$ref": "#/components/schemas/TracerConfig"
"$ref": "#/components/schemas/TracerConfigWrapper"
},
"description": "Configuration object for the tracer."
}
Expand Down
7 changes: 2 additions & 5 deletions packages/relay/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// SPDX-License-Identifier: Apache-2.0

import { TracerType } from './lib/constants';
import { JsonRpcError, predefined } from './lib/errors/JsonRpcError';
import { MirrorNodeClientError } from './lib/errors/MirrorNodeClientError';
import WebSocketError from './lib/errors/WebSocketError';
Expand All @@ -10,9 +8,9 @@ import {
IContractCallRequest,
IGetLogsParams,
INewFilterParams,
ITracerConfig,
ITransactionReceipt,
RequestDetails,
TransactionTracerConfig,
} from './lib/types';

export { JsonRpcError, predefined, MirrorNodeClientError, WebSocketError };
Expand All @@ -22,8 +20,7 @@ export { Relay } from './lib/relay';
export interface Debug {
traceTransaction: (
transactionIdOrHash: string,
tracer: TracerType,
tracerConfig: ITracerConfig,
tracerObject: TransactionTracerConfig,
requestDetails: RequestDetails,
) => Promise<any>;

Expand Down
22 changes: 15 additions & 7 deletions packages/relay/src/lib/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import {
EntityTraceStateMap,
ICallTracerConfig,
IOpcodeLoggerConfig,
ITracerConfig,
MirrorNodeContractResult,
ParamType,
RequestDetails,
TraceBlockByNumberTxResult,
TransactionTracerConfig,
} from './types';

/**
Expand Down Expand Up @@ -106,26 +106,34 @@ export class DebugImpl implements Debug {
@rpcMethod
@rpcParamValidationRules({
0: { type: ParamType.TRANSACTION_HASH_OR_ID, required: true },
1: { type: ParamType.COMBINED_TRACER_TYPE, required: false },
2: { type: ParamType.TRACER_CONFIG, required: false },
1: { type: ParamType.TRACER_CONFIG_WRAPPER, required: false },
})
@rpcParamLayoutConfig(RPC_LAYOUT.custom((params) => [params[0], params[1]]))
@cache(CacheService.getInstance(CACHE_LEVEL.L1))
async traceTransaction(
transactionIdOrHash: string,
tracer: TracerType,
tracerConfig: ITracerConfig,
tracerObject: TransactionTracerConfig,
requestDetails: RequestDetails,
): Promise<any> {
if (tracerObject?.tracer === TracerType.PrestateTracer) {
throw predefined.INVALID_PARAMETER(1, 'Prestate tracer is not yet supported on debug_traceTransaction');
}

if (this.logger.isLevelEnabled('trace')) {
this.logger.trace(`${requestDetails.formattedRequestId} traceTransaction(${transactionIdOrHash})`);
}

//we use a wrapper since we accept a transaction where a second param with tracer/tracerConfig may not be provided
//and we will still default to opcodeLogger
const tracer = tracerObject?.tracer ?? TracerType.OpcodeLogger;
const tracerConfig = tracerObject?.tracerConfig ?? {};

try {
DebugImpl.requireDebugAPIEnabled();
if (tracer === TracerType.CallTracer) {
return await this.callTracer(transactionIdOrHash, tracerConfig as ICallTracerConfig, requestDetails);
} else if (tracer === TracerType.OpcodeLogger) {
return await this.callOpcodeLogger(transactionIdOrHash, tracerConfig as IOpcodeLoggerConfig, requestDetails);
}
return await this.callOpcodeLogger(transactionIdOrHash, tracerConfig as IOpcodeLoggerConfig, requestDetails);
} catch (e) {
throw this.common.genericErrorHandler(e);
}
Expand Down
14 changes: 9 additions & 5 deletions packages/relay/src/lib/types/debug.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
// SPDX-License-Identifier: Apache-2.0

import { TracerType } from '../constants';
import { ICallTracerConfig } from './ITracerConfig';
import { ICallTracerConfig, ITracerConfig } from './ITracerConfig';

/**
* Configuration object for block tracing operations.
*/
export interface BlockTracerConfig {
/** The type of tracer to use for block tracing. */
interface TracerConfig<T extends ITracerConfig = ITracerConfig> {
/** The type of tracer to use for tracing. */
tracer: TracerType;
/** Optional configuration for the call tracer. */
tracerConfig?: ICallTracerConfig;
/** Optional configuration for the tracer. */
tracerConfig?: T;
}

// Public exports
export type BlockTracerConfig = TracerConfig<ICallTracerConfig>;
export type TransactionTracerConfig = TracerConfig<ITracerConfig>;

/**
* Represents the state of an entity during a trace operation.
*/
Expand Down
38 changes: 37 additions & 1 deletion packages/relay/src/lib/validators/objectTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: Apache-2.0

import { Validator } from '.';
import { TracerType } from '../constants';
import { predefined } from '../errors/JsonRpcError';
import {
ICallTracerConfig,
Expand All @@ -9,6 +9,7 @@ import {
IOpcodeLoggerConfig,
ITracerConfigWrapper,
} from '../types';
import { Validator } from '.';

export const OBJECTS_VALIDATIONS: { [key: string]: IObjectSchema } = {
blockHashObject: {
Expand Down Expand Up @@ -305,4 +306,39 @@ export class TracerConfigWrapper extends DefaultValidation<ITracerConfigWrapper>
constructor(config: any) {
super(OBJECTS_VALIDATIONS.tracerConfigWrapper, config);
}

validate() {
const valid = super.validate();

const { tracer, tracerConfig } = this.object;

if (!tracerConfig) {
return valid;
}

const callTracerKeys = Object.keys(OBJECTS_VALIDATIONS.callTracerConfig.properties);
const opcodeLoggerKeys = Object.keys(OBJECTS_VALIDATIONS.opcodeLoggerConfig.properties);

const configKeys = Object.keys(tracerConfig);
const hasCallTracerKeys = configKeys.some((k) => callTracerKeys.includes(k));
const hasOpcodeLoggerKeys = configKeys.some((k) => opcodeLoggerKeys.includes(k));

// we want to accept ICallTracerConfig only if the tracer is callTracer
// this config is not valid for opcodeLogger and vice versa
// accept only IOpcodeLoggerConfig with opcodeLogger tracer
if (hasCallTracerKeys && tracer === TracerType.OpcodeLogger) {
throw predefined.INVALID_PARAMETER(
1,
`callTracer 'tracerConfig' for ${this.name()} is only valid when tracer=${TracerType.CallTracer}`,
);
}

if (hasOpcodeLoggerKeys && tracer !== TracerType.OpcodeLogger) {
throw predefined.INVALID_PARAMETER(
1,
`opcodeLogger 'tracerConfig' for ${this.name()} is only valid when tracer=${TracerType.OpcodeLogger}`,
);
}
return valid;
}
}
30 changes: 2 additions & 28 deletions packages/relay/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import createHash from 'keccak';
import { Logger } from 'pino';

import { hexToASCII, prepend0x, strip0x } from './formatters';
import constants, { TracerType } from './lib/constants';
import constants from './lib/constants';
import { RPC_LAYOUT, RPC_PARAM_LAYOUT_KEY } from './lib/decorators';
import { ITracerConfig, RequestDetails } from './lib/types';
import { TYPES } from './lib/validators';
import { RequestDetails } from './lib/types';

export class Utils {
public static readonly IP_ADDRESS_REGEX = /\b((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}\b/g;
Expand Down Expand Up @@ -174,31 +173,6 @@ export class Utils {
public static arrangeRpcParams(method: Function, rpcParams: any[] = [], requestDetails: RequestDetails): any[] {
const layout = method[RPC_PARAM_LAYOUT_KEY];

if (method.name === 'traceTransaction') {
const transactionIdOrHash = rpcParams[0];
let tracer: TracerType = TracerType.OpcodeLogger;
let tracerConfig: ITracerConfig = {};

// Second param can be either a TracerType string, or an object for TracerConfig or TracerConfigWrapper
if (TYPES.tracerType.test(rpcParams[1])) {
tracer = rpcParams[1];
if (TYPES.tracerConfig.test(rpcParams[2])) {
tracerConfig = rpcParams[2];
}
} else if (TYPES.tracerConfig.test(rpcParams[1])) {
tracerConfig = rpcParams[1];
} else if (TYPES.tracerConfigWrapper.test(rpcParams[1])) {
if (TYPES.tracerType.test(rpcParams[1].tracer)) {
tracer = rpcParams[1].tracer;
}
if (TYPES.tracerConfig.test(rpcParams[1].tracerConfig)) {
tracerConfig = rpcParams[1].tracerConfig;
}
}

return [transactionIdOrHash, tracer, tracerConfig, requestDetails];
}

// Method only needs requestDetails
if (layout === RPC_LAYOUT.REQUEST_DETAILS_ONLY) {
return [requestDetails];
Expand Down
20 changes: 9 additions & 11 deletions packages/relay/tests/lib/debug.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ describe('Debug API Test Suite', async function () {
const tracerConfigFalse = { onlyTopCall: false };
const callTracer: TracerType = TracerType.CallTracer;
const opcodeLogger: TracerType = TracerType.OpcodeLogger;
const tracerObjectCallTracerFalse = { tracer: callTracer, tracerConfig: tracerConfigFalse };
const tracerObjectCallTracerTrue = { tracer: callTracer, tracerConfig: tracerConfigTrue };
const CONTRACTS_RESULTS_OPCODES = `contracts/results/${transactionHash}/opcodes`;
const CONTARCTS_RESULTS_ACTIONS = `contracts/results/${transactionHash}/actions`;
const CONTRACTS_RESULTS_BY_HASH = `contracts/results/${transactionHash}`;
Expand Down Expand Up @@ -362,8 +364,7 @@ describe('Debug API Test Suite', async function () {
it('should successfully debug a transaction', async function () {
const traceTransaction = await debugService.traceTransaction(
transactionHash,
callTracer,
tracerConfigFalse,
tracerObjectCallTracerFalse,
requestDetails,
);
expect(traceTransaction).to.exist;
Expand Down Expand Up @@ -396,8 +397,7 @@ describe('Debug API Test Suite', async function () {

const result = await debugService.traceTransaction(
transactionHash,
callTracer,
tracerConfigFalse,
tracerObjectCallTracerFalse,
requestDetails,
);

Expand All @@ -418,8 +418,7 @@ describe('Debug API Test Suite', async function () {
};
const result = await debugService.traceTransaction(
transactionHash,
callTracer,
tracerConfigTrue,
tracerObjectCallTracerTrue,
requestDetails,
);

Expand All @@ -431,8 +430,7 @@ describe('Debug API Test Suite', async function () {

const result = await debugService.traceTransaction(
transactionHash,
callTracer,
tracerConfigFalse,
tracerObjectCallTracerFalse,
requestDetails,
);

Expand Down Expand Up @@ -472,7 +470,8 @@ describe('Debug API Test Suite', async function () {
})),
};

const result = await debugService.traceTransaction(transactionHash, opcodeLogger, config, requestDetails);
const tracerObject = { tracer: opcodeLogger, tracerConfig: config };
const result = await debugService.traceTransaction(transactionHash, tracerObject, requestDetails);

expect(result).to.deep.equal(expectedResult);
});
Expand Down Expand Up @@ -508,8 +507,7 @@ describe('Debug API Test Suite', async function () {

await RelayAssertions.assertRejection(expectedError, debugService.traceTransaction, true, debugService, [
nonExistentTransactionHash,
callTracer,
tracerConfigTrue,
tracerObjectCallTracerTrue,
requestDetails,
]);
});
Expand Down
Loading
Loading