Skip to content

Commit b258042

Browse files
committed
feat: added test for debug_traceBlockByNumber
Signed-off-by: Logan Nguyen <logan.nguyen@swirldslabs.com>
1 parent 7f10c67 commit b258042

File tree

2 files changed

+254
-1
lines changed

2 files changed

+254
-1
lines changed

packages/relay/src/lib/debug.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ export class DebugImpl implements Debug {
166166
DebugImpl.requireDebugAPIEnabled();
167167
const blockResponse = await this.common.getHistoricalBlockResponse(requestDetails, blockNumber, true);
168168

169-
if (blockResponse == null) return [];
169+
if (blockResponse == null) throw predefined.RESOURCE_NOT_FOUND(`Block ${blockNumber} not found`);
170170

171171
const cacheKey = `${constants.CACHE_KEY.DEBUG_TRACE_BLOCK_BY_NUMBER}_${blockResponse.number}_${JSON.stringify(
172172
tracerObject,

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

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { HbarLimitService } from '../../src/lib/services/hbarLimitService';
2424
import { RequestDetails } from '../../src/lib/types';
2525
import RelayAssertions from '../assertions';
2626
import { getQueryParams, withOverriddenEnvsInMochaTest } from '../helpers';
27+
import { CommonService } from '../../src/lib/services';
2728
chai.use(chaiAsPromised);
2829

2930
const logger = pino({ level: 'silent' });
@@ -527,6 +528,258 @@ describe('Debug API Test Suite', async function () {
527528
});
528529
});
529530

531+
describe('debug_traceBlockByNumber', async function () {
532+
const blockNumber = '0x123';
533+
const blockNumberInDecimal = 291;
534+
const blockResponse = {
535+
number: blockNumberInDecimal,
536+
timestamp: {
537+
from: '1696438000.000000000',
538+
to: '1696438020.000000000',
539+
},
540+
};
541+
const contractResult1 = {
542+
hash: '0xabc123',
543+
result: 'SUCCESS',
544+
};
545+
const contractResult2 = {
546+
hash: '0xdef456',
547+
result: 'SUCCESS',
548+
};
549+
const contractResultWrongNonce = {
550+
hash: '0xghi789',
551+
result: 'WRONG_NONCE',
552+
};
553+
const callTracerResult1 = {
554+
type: 'CREATE',
555+
from: '0xc37f417fa09933335240fca72dd257bfbde9c275',
556+
to: '0x637a6a8e5a69c087c24983b05261f63f64ed7e9b',
557+
value: '0x0',
558+
gas: '0x493e0',
559+
gasUsed: '0x3a980',
560+
input: '0x1',
561+
output: '0x2',
562+
};
563+
const callTracerResult2 = {
564+
type: 'CALL',
565+
from: '0xc37f417fa09933335240fca72dd257bfbde9c275',
566+
to: '0x91b1c451777122afc9b83f9b96160d7e59847ad7',
567+
value: '0x0',
568+
gas: '0x493e0',
569+
gasUsed: '0x3a980',
570+
input: '0x3',
571+
output: '0x4',
572+
};
573+
const prestateTracerResult1 = {
574+
'0xc37f417fa09933335240fca72dd257bfbde9c275': {
575+
balance: '0x100000000',
576+
nonce: 2,
577+
code: '0x',
578+
storage: {},
579+
},
580+
};
581+
const prestateTracerResult2 = {
582+
'0x91b1c451777122afc9b83f9b96160d7e59847ad7': {
583+
balance: '0x200000000',
584+
nonce: 1,
585+
code: '0x608060405234801561001057600080fd5b50600436106100415760003560e01c8063',
586+
storage: {
587+
'0x0': '0x1',
588+
'0x1': '0x2',
589+
},
590+
},
591+
};
592+
593+
beforeEach(() => {
594+
sinon.restore();
595+
restMock.reset();
596+
web3Mock.reset();
597+
cacheService.clear(requestDetails);
598+
});
599+
600+
withOverriddenEnvsInMochaTest({ DEBUG_API_ENABLED: undefined }, () => {
601+
it('should throw UNSUPPORTED_METHOD', async function () {
602+
await RelayAssertions.assertRejection(
603+
predefined.UNSUPPORTED_METHOD,
604+
debugService.traceBlockByNumber,
605+
true,
606+
debugService,
607+
[blockNumber, { tracer: TracerType.CallTracer, tracerConfig: { onlyTopCall: false } }, requestDetails],
608+
);
609+
});
610+
});
611+
612+
withOverriddenEnvsInMochaTest({ DEBUG_API_ENABLED: false }, () => {
613+
it('should throw UNSUPPORTED_METHOD', async function () {
614+
await RelayAssertions.assertRejection(
615+
predefined.UNSUPPORTED_METHOD,
616+
debugService.traceBlockByNumber,
617+
true,
618+
debugService,
619+
[blockNumber, { tracer: TracerType.CallTracer, tracerConfig: { onlyTopCall: false } }, requestDetails],
620+
);
621+
});
622+
});
623+
624+
withOverriddenEnvsInMochaTest({ DEBUG_API_ENABLED: true }, () => {
625+
it('should return empty array if block is not found', async function () {
626+
// Stub CommonService.getHistoricalBlockResponse
627+
const getHistoricalBlockResponseStub = sinon.stub().resolves(null);
628+
sinon.stub(CommonService.prototype, 'getHistoricalBlockResponse').callsFake(getHistoricalBlockResponseStub);
629+
630+
try {
631+
await debugService.traceBlockByNumber(
632+
blockNumber,
633+
{ tracer: TracerType.CallTracer, tracerConfig: { onlyTopCall: false } },
634+
requestDetails,
635+
);
636+
expect.fail('Expected the traceBlockByNumber to throw an error but it did not');
637+
} catch (error) {
638+
expect(error.code).to.equal(predefined.RESOURCE_NOT_FOUND().code);
639+
expect(error.message).to.include(`Block ${blockNumber} not found`);
640+
}
641+
});
642+
643+
it('should return empty array if no contract results are found for the block', async function () {
644+
// Stub CommonService.getHistoricalBlockResponse
645+
const getHistoricalBlockResponseStub = sinon.stub().resolves(blockResponse);
646+
sinon.stub(CommonService.prototype, 'getHistoricalBlockResponse').callsFake(getHistoricalBlockResponseStub);
647+
648+
// Stub MirrorNodeClient.getContractResultWithRetry
649+
sinon.stub(mirrorNodeInstance, 'getContractResultWithRetry').resolves([]);
650+
651+
const result = await debugService.traceBlockByNumber(
652+
blockNumber,
653+
{ tracer: TracerType.CallTracer, tracerConfig: { onlyTopCall: false } },
654+
requestDetails,
655+
);
656+
657+
expect(result).to.be.an('array').that.is.empty;
658+
});
659+
660+
it('should return cached result if available', async function () {
661+
const cachedResult = [{ txHash: '0xabc123', result: callTracerResult1 }];
662+
663+
// Stub CommonService.getHistoricalBlockResponse
664+
const getHistoricalBlockResponseStub = sinon.stub().resolves(blockResponse);
665+
sinon.stub(CommonService.prototype, 'getHistoricalBlockResponse').callsFake(getHistoricalBlockResponseStub);
666+
667+
// Stub CacheService.getAsync
668+
sinon.stub(cacheService, 'getAsync').resolves(cachedResult);
669+
670+
const result = await debugService.traceBlockByNumber(
671+
blockNumber,
672+
{ tracer: TracerType.CallTracer, tracerConfig: { onlyTopCall: false } },
673+
requestDetails,
674+
);
675+
676+
expect(result).to.deep.equal(cachedResult);
677+
});
678+
679+
describe('with CallTracer', async function () {
680+
beforeEach(() => {
681+
// Stub CommonService.getHistoricalBlockResponse
682+
const getHistoricalBlockResponseStub = sinon.stub().resolves(blockResponse);
683+
sinon.stub(CommonService.prototype, 'getHistoricalBlockResponse').callsFake(getHistoricalBlockResponseStub);
684+
685+
// Stub CacheService methods
686+
sinon.stub(cacheService, 'getAsync').resolves(null);
687+
sinon.stub(cacheService, 'set').resolves();
688+
});
689+
690+
it('should trace block with CallTracer and filter out WRONG_NONCE results', async function () {
691+
sinon
692+
.stub(mirrorNodeInstance, 'getContractResultWithRetry')
693+
.resolves([contractResult1, contractResult2, contractResultWrongNonce]);
694+
695+
sinon
696+
.stub(debugService, 'callTracer')
697+
.withArgs(contractResult1.hash, sinon.match.any, sinon.match.any)
698+
.resolves(callTracerResult1)
699+
.withArgs(contractResult2.hash, sinon.match.any, sinon.match.any)
700+
.resolves(callTracerResult2);
701+
702+
const result = await debugService.traceBlockByNumber(
703+
blockNumber,
704+
{ tracer: TracerType.CallTracer, tracerConfig: { onlyTopCall: false } },
705+
requestDetails,
706+
);
707+
708+
expect(result).to.be.an('array').with.lengthOf(2);
709+
expect(result[0]).to.deep.equal({ txHash: contractResult1.hash, result: callTracerResult1 });
710+
expect(result[1]).to.deep.equal({ txHash: contractResult2.hash, result: callTracerResult2 });
711+
});
712+
713+
it('should use default CallTracer when no tracer is specified', async function () {
714+
sinon.stub(mirrorNodeInstance, 'getContractResultWithRetry').resolves([contractResult1]);
715+
sinon.stub(debugService, 'callTracer').resolves(callTracerResult1);
716+
717+
// Pass undefined with type assertion for the second parameter
718+
// In the implementation, undefined tracerObject triggers default behavior (using CallTracer)
719+
// TypeScript requires type assertion since the parameter is normally required
720+
const result = await debugService.traceBlockByNumber(blockNumber, undefined as any, requestDetails);
721+
722+
expect(result).to.be.an('array').with.lengthOf(1);
723+
expect(result[0]).to.deep.equal({ txHash: contractResult1.hash, result: callTracerResult1 });
724+
});
725+
});
726+
727+
describe('with PrestateTracer', async function () {
728+
beforeEach(() => {
729+
// Stub CommonService.getHistoricalBlockResponse
730+
const getHistoricalBlockResponseStub = sinon.stub().resolves(blockResponse);
731+
sinon.stub(CommonService.prototype, 'getHistoricalBlockResponse').callsFake(getHistoricalBlockResponseStub);
732+
733+
// Stub CacheService methods
734+
sinon.stub(cacheService, 'getAsync').resolves(null);
735+
sinon.stub(cacheService, 'set').resolves();
736+
});
737+
738+
it('should trace block with PrestateTracer and filter out WRONG_NONCE results', async function () {
739+
sinon
740+
.stub(mirrorNodeInstance, 'getContractResultWithRetry')
741+
.resolves([contractResult1, contractResult2, contractResultWrongNonce]);
742+
743+
sinon
744+
.stub(debugService, 'prestateTracer')
745+
.withArgs(contractResult1.hash, sinon.match.any, sinon.match.any)
746+
.resolves(prestateTracerResult1)
747+
.withArgs(contractResult2.hash, sinon.match.any, sinon.match.any)
748+
.resolves(prestateTracerResult2);
749+
750+
const result = await debugService.traceBlockByNumber(
751+
blockNumber,
752+
{ tracer: TracerType.PrestateTracer, tracerConfig: { onlyTopCall: true } },
753+
requestDetails,
754+
);
755+
756+
expect(result).to.be.an('array').with.lengthOf(2);
757+
expect(result[0]).to.deep.equal({ txHash: contractResult1.hash, result: prestateTracerResult1 });
758+
expect(result[1]).to.deep.equal({ txHash: contractResult2.hash, result: prestateTracerResult2 });
759+
});
760+
});
761+
762+
it('should handle error scenarios', async function () {
763+
// Create a proper JsonRpcError
764+
const jsonRpcError = predefined.INTERNAL_ERROR('Test error');
765+
766+
// Stub CommonService.getHistoricalBlockResponse to throw error
767+
const getHistoricalBlockResponseStub = sinon.stub().throws(jsonRpcError);
768+
sinon.stub(CommonService.prototype, 'getHistoricalBlockResponse').callsFake(getHistoricalBlockResponseStub);
769+
770+
// Stub CommonService.genericErrorHandler to return the error
771+
const genericErrorHandlerStub = sinon.stub().returns(jsonRpcError);
772+
sinon.stub(CommonService.prototype, 'genericErrorHandler').callsFake(genericErrorHandlerStub);
773+
774+
await RelayAssertions.assertRejection(jsonRpcError, debugService.traceBlockByNumber, true, debugService, [
775+
blockNumber,
776+
{ tracer: TracerType.CallTracer },
777+
requestDetails,
778+
]);
779+
});
780+
});
781+
});
782+
530783
describe('prestateTracer', async function () {
531784
const mockTimestamp = '1696438011.462526383';
532785
const contractId = '0.0.1033';

0 commit comments

Comments
 (0)