@@ -24,6 +24,7 @@ import { HbarLimitService } from '../../src/lib/services/hbarLimitService';
24
24
import { RequestDetails } from '../../src/lib/types' ;
25
25
import RelayAssertions from '../assertions' ;
26
26
import { getQueryParams , withOverriddenEnvsInMochaTest } from '../helpers' ;
27
+ import { CommonService } from '../../src/lib/services' ;
27
28
chai . use ( chaiAsPromised ) ;
28
29
29
30
const logger = pino ( { level : 'silent' } ) ;
@@ -527,6 +528,258 @@ describe('Debug API Test Suite', async function () {
527
528
} ) ;
528
529
} ) ;
529
530
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
+
530
783
describe ( 'prestateTracer' , async function ( ) {
531
784
const mockTimestamp = '1696438011.462526383' ;
532
785
const contractId = '0.0.1033' ;
0 commit comments