Skip to content

Commit 642df65

Browse files
chore: Adds prestate tracer to traceTransaction endpoint (#3898)
Signed-off-by: Konstantina Blazhukova <[email protected]>
1 parent f68c902 commit 642df65

File tree

5 files changed

+187
-14
lines changed

5 files changed

+187
-14
lines changed

docs/openrpc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1563,7 +1563,7 @@
15631563
"TracerType": {
15641564
"title": "Tracer type",
15651565
"type": "string",
1566-
"enum": ["callTracer", "opcodeLogger"]
1566+
"enum": ["callTracer", "opcodeLogger", "prestateTracer"]
15671567
},
15681568
"TracerConfigWrapper": {
15691569
"title": "Tracer config wrapper",

packages/relay/src/lib/debug.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,6 @@ export class DebugImpl implements Debug {
115115
tracerObject: TransactionTracerConfig,
116116
requestDetails: RequestDetails,
117117
): Promise<any> {
118-
if (tracerObject?.tracer === TracerType.PrestateTracer) {
119-
throw predefined.INVALID_PARAMETER(1, 'Prestate tracer is not yet supported on debug_traceTransaction');
120-
}
121-
122118
if (this.logger.isLevelEnabled('trace')) {
123119
this.logger.trace(`${requestDetails.formattedRequestId} traceTransaction(${transactionIdOrHash})`);
124120
}
@@ -134,6 +130,11 @@ export class DebugImpl implements Debug {
134130
return await this.callTracer(transactionIdOrHash, tracerConfig as ICallTracerConfig, requestDetails);
135131
}
136132

133+
if (tracer === TracerType.PrestateTracer) {
134+
const onlyTopCall = (tracerObject?.tracerConfig as ICallTracerConfig)?.onlyTopCall ?? false;
135+
return await this.prestateTracer(transactionIdOrHash, onlyTopCall, requestDetails);
136+
}
137+
137138
if (!ConfigService.get('OPCODELOGGER_ENABLED')) {
138139
throw predefined.UNSUPPORTED_METHOD;
139140
}

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

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,100 @@ describe('Debug API Test Suite', async function () {
489489
}
490490
});
491491

492+
describe('prestateTracer', async function () {
493+
const prestateTracer: TracerType = TracerType.PrestateTracer;
494+
const mockPrestateResult = {
495+
'0xc37f417fa09933335240fca72dd257bfbde9c275': {
496+
balance: '0x100000000',
497+
nonce: 2,
498+
code: '0x',
499+
storage: {},
500+
},
501+
'0x637a6a8e5a69c087c24983b05261f63f64ed7e9b': {
502+
balance: '0x200000000',
503+
nonce: 1,
504+
code: '0x608060405234801561001057600080fd5b50600436106100415760003560e01c8063',
505+
storage: {
506+
'0x0': '0x1',
507+
'0x1': '0x2',
508+
},
509+
},
510+
};
511+
512+
beforeEach(() => {
513+
sinon.stub(debugService, 'prestateTracer').resolves(mockPrestateResult);
514+
});
515+
516+
afterEach(() => {
517+
sinon.restore();
518+
});
519+
520+
it('should successfully trace transaction with prestateTracer', async function () {
521+
const tracerObject = { tracer: prestateTracer };
522+
const result = await debugService.traceTransaction(transactionHash, tracerObject, requestDetails);
523+
524+
expect(result).to.deep.equal(mockPrestateResult);
525+
expect(result).to.be.an('object');
526+
expect(Object.keys(result)).to.have.lengthOf(2);
527+
528+
for (const address of Object.keys(result)) {
529+
expect(result[address]).to.have.all.keys(['balance', 'nonce', 'code', 'storage']);
530+
expect(result[address].nonce).to.be.a('number');
531+
expect(result[address].code).to.exist;
532+
expect(result[address].storage).to.be.an('object');
533+
}
534+
});
535+
536+
it('should trace transaction with prestateTracer and onlyTopCall=true', async function () {
537+
const tracerObject = { tracer: prestateTracer, tracerConfig: { onlyTopCall: true } };
538+
const result = await debugService.traceTransaction(transactionHash, tracerObject, requestDetails);
539+
540+
expect(result).to.deep.equal(mockPrestateResult);
541+
542+
// Verify that prestateTracer was called with onlyTopCall=true
543+
const prestateTracerStub = debugService.prestateTracer as sinon.SinonStub;
544+
expect(prestateTracerStub.calledOnce).to.be.true;
545+
expect(prestateTracerStub.calledWith(transactionHash, true, requestDetails)).to.be.true;
546+
});
547+
548+
it('should trace transaction with prestateTracer and onlyTopCall=false (default)', async function () {
549+
const tracerObject = { tracer: prestateTracer, tracerConfig: { onlyTopCall: false } };
550+
const result = await debugService.traceTransaction(transactionHash, tracerObject, requestDetails);
551+
552+
expect(result).to.deep.equal(mockPrestateResult);
553+
554+
// Verify that prestateTracer was called with onlyTopCall=false
555+
const prestateTracerStub = debugService.prestateTracer as sinon.SinonStub;
556+
expect(prestateTracerStub.calledOnce).to.be.true;
557+
expect(prestateTracerStub.calledWith(transactionHash, false, requestDetails)).to.be.true;
558+
});
559+
560+
it('should handle empty prestate result', async function () {
561+
const emptyResult = {};
562+
(debugService.prestateTracer as sinon.SinonStub).resolves(emptyResult);
563+
564+
const tracerObject = { tracer: prestateTracer };
565+
const result = await debugService.traceTransaction(transactionHash, tracerObject, requestDetails);
566+
567+
expect(result).to.deep.equal(emptyResult);
568+
expect(result).to.be.an('object');
569+
expect(Object.keys(result)).to.have.lengthOf(0);
570+
});
571+
572+
it('should propagate errors from prestateTracer', async function () {
573+
const expectedError = predefined.RESOURCE_NOT_FOUND('Failed to retrieve contract results');
574+
(debugService.prestateTracer as sinon.SinonStub).rejects(expectedError);
575+
576+
const tracerObject = { tracer: prestateTracer };
577+
578+
await RelayAssertions.assertRejection(expectedError, debugService.traceTransaction, true, debugService, [
579+
transactionHash,
580+
tracerObject,
581+
requestDetails,
582+
]);
583+
});
584+
});
585+
492586
describe('Invalid scenarios', async function () {
493587
let notFound: { _status: { messages: { message: string }[] } };
494588

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

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,71 @@ describe('@debug API Acceptance Tests', function () {
417417
});
418418
});
419419

420+
describe('PrestateTracer', () => {
421+
it('should trace a transaction using PrestateTracer', async function () {
422+
const result = await relay.call(
423+
DEBUG_TRACE_TRANSACTION,
424+
[createChildTx.hash, TRACER_CONFIGS.PRESTATE_TRACER],
425+
requestId,
426+
);
427+
428+
expect(result).to.be.an('object');
429+
expect(Object.keys(result).length).to.be.at.least(1);
430+
431+
// Check that the result contains prestate information for at least the contract and sender
432+
const keys = Object.keys(result);
433+
expect(keys.length).to.be.at.least(2);
434+
435+
// For each address in the result, check it has the expected fields
436+
for (const address of keys) {
437+
const state = result[address];
438+
Assertions.validatePrestateTracerResult(state);
439+
}
440+
});
441+
442+
it('should trace a transaction using PrestateTracer with onlyTopCall=true', async function () {
443+
const result = await relay.call(
444+
DEBUG_TRACE_TRANSACTION,
445+
[createChildTx.hash, TRACER_CONFIGS.PRESTATE_TRACER_TOP_ONLY],
446+
requestId,
447+
);
448+
449+
expect(result).to.be.an('object');
450+
expect(Object.keys(result).length).to.be.at.least(1);
451+
452+
// Check that the result contains prestate information for at least the contract and sender
453+
const keys = Object.keys(result);
454+
expect(keys.length).to.be.at.least(2);
455+
456+
// For each address in the result, check it has the expected fields
457+
for (const address of keys) {
458+
const state = result[address];
459+
Assertions.validatePrestateTracerResult(state);
460+
}
461+
});
462+
463+
it('should trace a transaction using PrestateTracer with onlyTopCall=false', async function () {
464+
const result = await relay.call(
465+
DEBUG_TRACE_TRANSACTION,
466+
[createChildTx.hash, TRACER_CONFIGS.PRESTATE_TRACER_TOP_ONLY_FALSE],
467+
requestId,
468+
);
469+
470+
expect(result).to.be.an('object');
471+
expect(Object.keys(result).length).to.be.at.least(1);
472+
473+
// Check that the result contains prestate information for at least the contract and sender
474+
const keys = Object.keys(result);
475+
expect(keys.length).to.be.at.least(2);
476+
477+
// For each address in the result, check it has the expected fields
478+
for (const address of keys) {
479+
const state = result[address];
480+
Assertions.validatePrestateTracerResult(state);
481+
}
482+
});
483+
});
484+
420485
describe('OpcodeLogger', () => {
421486
it('@release should trace a successful transaction using OpcodeLogger (default when no tracer specified)', async function () {
422487
const result = await relay.call(DEBUG_TRACE_TRANSACTION, [createChildTx.hash], requestId);
@@ -546,15 +611,6 @@ describe('@debug API Acceptance Tests', function () {
546611
);
547612
});
548613

549-
it('should fail with INVALID_PARAMETER when using PrestateTracer', async function () {
550-
await relay.callFailing(
551-
DEBUG_TRACE_TRANSACTION,
552-
[createChildTx.hash, TRACER_CONFIGS.PRESTATE_TRACER],
553-
predefined.INVALID_PARAMETER(1, 'Prestate tracer is not yet supported on debug_traceTransaction'),
554-
requestId,
555-
);
556-
});
557-
558614
it('should fail with INVALID_PARAMETER when given an invalid tracer type', async function () {
559615
const invalidTracerConfig = { tracer: 'InvalidTracer' };
560616
await relay.callFailing(

packages/server/tests/integration/server.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2506,6 +2506,28 @@ describe('RPC Server', function () {
25062506
).to.not.throw;
25072507
});
25082508

2509+
it('should execute with PrestateTracer type and valid PrestateTracerConfig', async () => {
2510+
expect(
2511+
await testClient.post('/', {
2512+
jsonrpc: '2.0',
2513+
method: 'debug_traceTransaction',
2514+
params: [contractHash1, { tracer: TracerType.PrestateTracer }],
2515+
id: 1,
2516+
}),
2517+
).to.not.throw;
2518+
});
2519+
2520+
it('should execute with PrestateTracer type and onlyTopCall option', async () => {
2521+
expect(
2522+
await testClient.post('/', {
2523+
jsonrpc: '2.0',
2524+
method: 'debug_traceTransaction',
2525+
params: [contractHash1, { tracer: TracerType.PrestateTracer, tracerConfig: { onlyTopCall: true } }],
2526+
id: 1,
2527+
}),
2528+
).to.not.throw;
2529+
});
2530+
25092531
it('should execute with valid hash', async () => {
25102532
expect(
25112533
await testClient.post('/', {

0 commit comments

Comments
 (0)