Skip to content

Commit 3e442bd

Browse files
committed
fix: debug_traceTransaction validation for default tracer
Signed-off-by: Simeon Nakov <[email protected]>
1 parent c5c5433 commit 3e442bd

File tree

5 files changed

+218
-20
lines changed

5 files changed

+218
-20
lines changed

packages/relay/src/lib/debug.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,26 @@ export class DebugImpl implements Debug {
122122
//we use a wrapper since we accept a transaction where a second param with tracer/tracerConfig may not be provided
123123
//and we will still default to opcodeLogger
124124
const tracer = tracerObject?.tracer ?? TracerType.OpcodeLogger;
125-
const tracerConfig = tracerObject?.tracerConfig ?? {};
125+
126+
// Extract tracer config from either nested tracerConfig or top-level properties
127+
let tracerConfig = tracerObject?.tracerConfig ?? {};
128+
129+
// If no nested tracerConfig is provided, check for top-level tracer config properties
130+
if (!tracerObject?.tracerConfig && tracerObject) {
131+
const { tracer: _, tracerConfig: __, ...topLevelConfig } = tracerObject;
132+
// Only include valid tracer config properties
133+
const validConfigKeys = ['onlyTopCall', 'enableMemory', 'disableStack', 'disableStorage'];
134+
const filteredConfig = Object.keys(topLevelConfig)
135+
.filter((key) => validConfigKeys.includes(key))
136+
.reduce((obj, key) => {
137+
obj[key] = topLevelConfig[key];
138+
return obj;
139+
}, {} as any);
140+
141+
if (Object.keys(filteredConfig).length > 0) {
142+
tracerConfig = filteredConfig;
143+
}
144+
}
126145

127146
try {
128147
DebugImpl.requireDebugAPIEnabled();
@@ -131,7 +150,7 @@ export class DebugImpl implements Debug {
131150
}
132151

133152
if (tracer === TracerType.PrestateTracer) {
134-
const onlyTopCall = (tracerObject?.tracerConfig as ICallTracerConfig)?.onlyTopCall ?? false;
153+
const onlyTopCall = (tracerConfig as ICallTracerConfig)?.onlyTopCall ?? false;
135154
return await this.prestateTracer(transactionIdOrHash, onlyTopCall, requestDetails);
136155
}
137156

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

33
import { TracerType } from '../constants';
4-
import { ITracerConfig } from './ITracerConfig';
4+
import { ICallTracerConfig, IOpcodeLoggerConfig, ITracerConfig } from './ITracerConfig';
55

6-
export interface ITracerConfigWrapper {
6+
export interface ITracerConfigWrapper extends Partial<ICallTracerConfig>, Partial<IOpcodeLoggerConfig> {
77
tracer?: TracerType;
88
tracerConfig?: ITracerConfig;
99
}

packages/relay/src/lib/validators/objectTypes.ts

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,28 @@ export const OBJECTS_VALIDATIONS: { [key: string]: IObjectSchema } = {
123123
nullable: false,
124124
required: false,
125125
},
126+
// CallTracer config properties at top level
127+
onlyTopCall: {
128+
type: 'boolean',
129+
nullable: false,
130+
required: false,
131+
},
132+
// OpcodeLogger config properties at top level
133+
enableMemory: {
134+
type: 'boolean',
135+
nullable: false,
136+
required: false,
137+
},
138+
disableStack: {
139+
type: 'boolean',
140+
nullable: false,
141+
required: false,
142+
},
143+
disableStorage: {
144+
type: 'boolean',
145+
nullable: false,
146+
required: false,
147+
},
126148
},
127149
},
128150
transaction: {
@@ -236,32 +258,51 @@ export function validateTracerConfigWrapper(param: any): boolean {
236258
const valid = validateSchema(schema, param);
237259
const { tracer, tracerConfig } = param;
238260

239-
if (!tracerConfig) {
240-
return valid;
241-
}
242-
243261
const callTracerKeys = Object.keys(OBJECTS_VALIDATIONS.callTracerConfig.properties);
244262
const opcodeLoggerKeys = Object.keys(OBJECTS_VALIDATIONS.opcodeLoggerConfig.properties);
245263

246-
const configKeys = Object.keys(tracerConfig);
247-
const hasCallTracerKeys = configKeys.some((k) => callTracerKeys.includes(k));
248-
const hasOpcodeLoggerKeys = configKeys.some((k) => opcodeLoggerKeys.includes(k));
264+
// Check for tracer config properties at the top level
265+
const topLevelKeys = Object.keys(param);
266+
const hasTopLevelCallTracerKeys = topLevelKeys.some((k) => callTracerKeys.includes(k));
267+
const hasTopLevelOpcodeLoggerKeys = topLevelKeys.some((k) => opcodeLoggerKeys.includes(k));
268+
269+
// Check for tracer config properties in nested tracerConfig
270+
let hasNestedCallTracerKeys = false;
271+
let hasNestedOpcodeLoggerKeys = false;
272+
if (tracerConfig) {
273+
const configKeys = Object.keys(tracerConfig);
274+
hasNestedCallTracerKeys = configKeys.some((k) => callTracerKeys.includes(k));
275+
hasNestedOpcodeLoggerKeys = configKeys.some((k) => opcodeLoggerKeys.includes(k));
276+
}
277+
278+
// Don't allow both top-level and nested config properties at the same time
279+
if ((hasTopLevelCallTracerKeys || hasTopLevelOpcodeLoggerKeys) && tracerConfig) {
280+
throw predefined.INVALID_PARAMETER(
281+
1,
282+
`Cannot specify tracer config properties both at top level and in 'tracerConfig' for ${schema.name}`,
283+
);
284+
}
285+
286+
// Determine which tracer type should be used
287+
const effectiveTracer = tracer ?? TracerType.OpcodeLogger; // Default to opcodeLogger if no tracer specified
249288

250-
// we want to accept ICallTracerConfig only if the tracer is callTracer
251-
// this config is not valid for opcodeLogger and vice versa
252-
// accept only IOpcodeLoggerConfig with opcodeLogger tracer
253-
if (hasCallTracerKeys && tracer === TracerType.OpcodeLogger) {
289+
// Validate that the right config properties are used with the right tracer
290+
const hasCallTracerKeys = hasTopLevelCallTracerKeys || hasNestedCallTracerKeys;
291+
const hasOpcodeLoggerKeys = hasTopLevelOpcodeLoggerKeys || hasNestedOpcodeLoggerKeys;
292+
293+
if (hasCallTracerKeys && effectiveTracer === TracerType.OpcodeLogger) {
254294
throw predefined.INVALID_PARAMETER(
255295
1,
256-
`callTracer 'tracerConfig' for ${schema.name} is only valid when tracer=${TracerType.CallTracer}`,
296+
`callTracer config properties for ${schema.name} are only valid when tracer=${TracerType.CallTracer}`,
257297
);
258298
}
259299

260-
if (hasOpcodeLoggerKeys && tracer !== TracerType.OpcodeLogger) {
300+
if (hasOpcodeLoggerKeys && effectiveTracer !== TracerType.OpcodeLogger) {
261301
throw predefined.INVALID_PARAMETER(
262302
1,
263-
`opcodeLogger 'tracerConfig' for ${schema.name} is only valid when tracer=${TracerType.OpcodeLogger}`,
303+
`opcodeLogger config properties for ${schema.name} are only valid when tracer=${TracerType.OpcodeLogger}`,
264304
);
265305
}
306+
266307
return valid;
267308
}

packages/relay/tests/lib/validators/validators.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,8 @@ describe('Validator', async () => {
672672
{ tracer: Constants.TracerType.CallTracer, tracerConfig: {} },
673673
{ tracer: Constants.TracerType.CallTracer },
674674
{ tracerConfig: { onlyTopCall: true } },
675+
// CallTracer config at top level
676+
{ tracer: Constants.TracerType.CallTracer, onlyTopCall: true },
675677
// OpcodeLoggerConfig
676678
{
677679
tracer: Constants.TracerType.OpcodeLogger,
@@ -687,6 +689,16 @@ describe('Validator', async () => {
687689
{ tracer: Constants.TracerType.OpcodeLogger, tracerConfig: {} },
688690
{ tracer: Constants.TracerType.OpcodeLogger },
689691
{ tracerConfig: { enableMemory: true, disableStack: false, disableStorage: true } },
692+
// OpcodeLogger config at top level
693+
{ tracer: Constants.TracerType.OpcodeLogger, enableMemory: true, disableStack: false, disableStorage: true },
694+
{ tracer: Constants.TracerType.OpcodeLogger, enableMemory: true },
695+
{ tracer: Constants.TracerType.OpcodeLogger, disableStack: false },
696+
{ tracer: Constants.TracerType.OpcodeLogger, disableStorage: true },
697+
// Top level opcodeLogger config without explicit tracer (defaults to opcodeLogger)
698+
{ enableMemory: true, disableStack: false, disableStorage: true },
699+
{ enableMemory: true },
700+
{ disableStack: false },
701+
{ disableStorage: true },
690702
// Empty object
691703
{},
692704
],
@@ -695,6 +707,51 @@ describe('Validator', async () => {
695707
input: { tracer: 'invalid', tracerConfig: {} },
696708
error: expectInvalidParam("'tracer' for TracerConfigWrapper", TYPES.tracerType.error, 'invalid'),
697709
},
710+
// CallTracer config properties with wrong tracer type
711+
{
712+
input: { tracer: Constants.TracerType.OpcodeLogger, onlyTopCall: true },
713+
error: expectInvalidParam(
714+
1,
715+
'callTracer config properties for TracerConfigWrapper are only valid when tracer=callTracer',
716+
),
717+
},
718+
{
719+
input: { onlyTopCall: true }, // defaults to opcodeLogger
720+
error: expectInvalidParam(
721+
1,
722+
'callTracer config properties for TracerConfigWrapper are only valid when tracer=callTracer',
723+
),
724+
},
725+
// OpcodeLogger config properties with wrong tracer type
726+
{
727+
input: { tracer: Constants.TracerType.CallTracer, enableMemory: true },
728+
error: expectInvalidParam(
729+
1,
730+
'opcodeLogger config properties for TracerConfigWrapper are only valid when tracer=opcodeLogger',
731+
),
732+
},
733+
{
734+
input: { tracer: Constants.TracerType.CallTracer, disableStack: false },
735+
error: expectInvalidParam(
736+
1,
737+
'opcodeLogger config properties for TracerConfigWrapper are only valid when tracer=opcodeLogger',
738+
),
739+
},
740+
// Both top-level and nested config
741+
{
742+
input: { enableMemory: true, tracerConfig: { disableStack: false } },
743+
error: expectInvalidParam(
744+
1,
745+
"Cannot specify tracer config properties both at top level and in 'tracerConfig' for TracerConfigWrapper",
746+
),
747+
},
748+
{
749+
input: { tracer: Constants.TracerType.CallTracer, onlyTopCall: true, tracerConfig: { onlyTopCall: false } },
750+
error: expectInvalidParam(
751+
1,
752+
"Cannot specify tracer config properties both at top level and in 'tracerConfig' for TracerConfigWrapper",
753+
),
754+
},
698755
{
699756
input: { tracer: Constants.TracerType.CallTracer, tracerConfig: { onlyTopCall: 'invalid' } },
700757
error: expectInvalidParam(

packages/server/tests/acceptance/debug.spec.ts

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ describe('@debug API Acceptance Tests', function () {
605605
[createChildTx.hash, invalidTracerConfig],
606606
predefined.INVALID_PARAMETER(
607607
1,
608-
"callTracer 'tracerConfig' for TracerConfigWrapper is only valid when tracer=callTracer",
608+
'callTracer config properties for TracerConfigWrapper are only valid when tracer=callTracer',
609609
),
610610
);
611611
});
@@ -620,7 +620,88 @@ describe('@debug API Acceptance Tests', function () {
620620
[createChildTx.hash, invalidTracerConfig],
621621
predefined.INVALID_PARAMETER(
622622
1,
623-
"opcodeLogger 'tracerConfig' for TracerConfigWrapper is only valid when tracer=opcodeLogger",
623+
'opcodeLogger config properties for TracerConfigWrapper are only valid when tracer=opcodeLogger',
624+
),
625+
);
626+
});
627+
628+
it('should support both nested and top-level tracer config parameter formats', async function () {
629+
// Test the nested format (original format)
630+
const nestedFormat = {
631+
tracer: TracerType.OpcodeLogger,
632+
tracerConfig: {
633+
enableMemory: true,
634+
disableStack: false,
635+
disableStorage: true,
636+
},
637+
};
638+
639+
const nestedResult = await relay.call(DEBUG_TRACE_TRANSACTION, [createChildTx.hash, nestedFormat]);
640+
expect(nestedResult).to.exist;
641+
expect(nestedResult.structLogs).to.exist;
642+
643+
// Test the top-level format (new format from our fix)
644+
const topLevelFormat = {
645+
enableMemory: true,
646+
disableStack: false,
647+
disableStorage: true,
648+
};
649+
650+
const topLevelResult = await relay.call(DEBUG_TRACE_TRANSACTION, [createChildTx.hash, topLevelFormat]);
651+
expect(topLevelResult).to.exist;
652+
expect(topLevelResult.structLogs).to.exist;
653+
654+
// Both formats should produce similar results
655+
expect(topLevelResult.structLogs).to.have.length.greaterThan(0);
656+
expect(nestedResult.structLogs).to.have.length.greaterThan(0);
657+
});
658+
659+
it('should reject mixed format (top-level and nested config)', async function () {
660+
const mixedFormat = {
661+
enableMemory: true,
662+
tracerConfig: {
663+
disableStack: false,
664+
},
665+
};
666+
667+
await relay.callFailing(
668+
DEBUG_TRACE_TRANSACTION,
669+
[createChildTx.hash, mixedFormat],
670+
predefined.INVALID_PARAMETER(
671+
1,
672+
"Cannot specify tracer config properties both at top level and in 'tracerConfig' for TracerConfigWrapper",
673+
),
674+
);
675+
});
676+
677+
it('should reject top-level callTracer config with wrong tracer type', async function () {
678+
const invalidFormat = {
679+
tracer: TracerType.OpcodeLogger,
680+
onlyTopCall: true, // callTracer property with opcodeLogger tracer
681+
};
682+
683+
await relay.callFailing(
684+
DEBUG_TRACE_TRANSACTION,
685+
[createChildTx.hash, invalidFormat],
686+
predefined.INVALID_PARAMETER(
687+
1,
688+
'callTracer config properties for TracerConfigWrapper are only valid when tracer=callTracer',
689+
),
690+
);
691+
});
692+
693+
it('should reject top-level opcodeLogger config with wrong tracer type', async function () {
694+
const invalidFormat = {
695+
tracer: TracerType.CallTracer,
696+
enableMemory: true, // opcodeLogger property with callTracer tracer
697+
};
698+
699+
await relay.callFailing(
700+
DEBUG_TRACE_TRANSACTION,
701+
[createChildTx.hash, invalidFormat],
702+
predefined.INVALID_PARAMETER(
703+
1,
704+
'opcodeLogger config properties for TracerConfigWrapper are only valid when tracer=opcodeLogger',
624705
),
625706
);
626707
});

0 commit comments

Comments
 (0)