@@ -472,3 +472,259 @@ func TestGetStateFork(t *testing.T) {
472
472
require .ErrorContains (t , "Invalid state ID: foo" , err )
473
473
})
474
474
}
475
+
476
+ func TestGetFinalityCheckpoints (t * testing.T ) {
477
+ ctx := context .Background ()
478
+
479
+ // We fill state and block roots with hex representations of natural numbers starting with 0.
480
+ // Example: 16 becomes 0x00...0f
481
+ fillRoots := func (state * pb.BeaconState ) {
482
+ rootsLen := params .MainnetConfig ().SlotsPerHistoricalRoot
483
+ roots := make ([][]byte , rootsLen )
484
+ for i := types .Slot (0 ); i < rootsLen ; i ++ {
485
+ roots [i ] = make ([]byte , 32 )
486
+ }
487
+ for j := 0 ; j < len (roots ); j ++ {
488
+ // Remove '0x' prefix and left-pad '0' to have 64 chars in total.
489
+ s := fmt .Sprintf ("%064s" , hexutil .EncodeUint64 (uint64 (j ))[2 :])
490
+ h , err := hexutil .Decode ("0x" + s )
491
+ require .NoError (t , err , "Failed to decode root " + s )
492
+ roots [j ] = h
493
+ }
494
+ state .StateRoots = roots
495
+ state .BlockRoots = roots
496
+ }
497
+ fillCheckpoints := func (state * pb.BeaconState ) {
498
+ state .PreviousJustifiedCheckpoint = & eth.Checkpoint {
499
+ Root : bytesutil .PadTo ([]byte ("previous" ), 32 ),
500
+ Epoch : 113 ,
501
+ }
502
+ state .CurrentJustifiedCheckpoint = & eth.Checkpoint {
503
+ Root : bytesutil .PadTo ([]byte ("current" ), 32 ),
504
+ Epoch : 123 ,
505
+ }
506
+ state .FinalizedCheckpoint = & eth.Checkpoint {
507
+ Root : bytesutil .PadTo ([]byte ("finalized" ), 32 ),
508
+ Epoch : 103 ,
509
+ }
510
+ }
511
+ headSlot := types .Slot (123 )
512
+ fillSlot := func (state * pb.BeaconState ) {
513
+ state .Slot = headSlot
514
+ }
515
+ state , err := testutil .NewBeaconState (fillCheckpoints , fillRoots , fillSlot )
516
+ require .NoError (t , err )
517
+ stateRoot , err := state .HashTreeRoot (ctx )
518
+ require .NoError (t , err )
519
+
520
+ t .Run ("Head" , func (t * testing.T ) {
521
+ s := Server {
522
+ ChainInfoFetcher : & chainMock.ChainService {State : state },
523
+ }
524
+
525
+ resp , err := s .GetFinalityCheckpoints (ctx , & ethpb.StateRequest {
526
+ StateId : []byte ("head" ),
527
+ })
528
+ require .NoError (t , err )
529
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("previous" ), 32 ), resp .Data .PreviousJustified .Root )
530
+ assert .Equal (t , types .Epoch (113 ), resp .Data .PreviousJustified .Epoch )
531
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("current" ), 32 ), resp .Data .CurrentJustified .Root )
532
+ assert .Equal (t , types .Epoch (123 ), resp .Data .CurrentJustified .Epoch )
533
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("finalized" ), 32 ), resp .Data .Finalized .Root )
534
+ assert .Equal (t , types .Epoch (103 ), resp .Data .Finalized .Epoch )
535
+ })
536
+
537
+ t .Run ("Genesis" , func (t * testing.T ) {
538
+ db := testDB .SetupDB (t )
539
+ b := testutil .NewBeaconBlock ()
540
+ b .Block .StateRoot = bytesutil .PadTo ([]byte ("genesis" ), 32 )
541
+ require .NoError (t , db .SaveBlock (ctx , b ))
542
+ r , err := b .Block .HashTreeRoot ()
543
+ require .NoError (t , err )
544
+ require .NoError (t , db .SaveStateSummary (ctx , & pb.StateSummary {Root : r [:]}))
545
+ require .NoError (t , db .SaveGenesisBlockRoot (ctx , r ))
546
+ st , err := testutil .NewBeaconState (func (state * pb.BeaconState ) {
547
+ state .PreviousJustifiedCheckpoint = & eth.Checkpoint {
548
+ Root : bytesutil .PadTo ([]byte ("previous" ), 32 ),
549
+ Epoch : 113 ,
550
+ }
551
+ state .CurrentJustifiedCheckpoint = & eth.Checkpoint {
552
+ Root : bytesutil .PadTo ([]byte ("current" ), 32 ),
553
+ Epoch : 123 ,
554
+ }
555
+ state .FinalizedCheckpoint = & eth.Checkpoint {
556
+ Root : bytesutil .PadTo ([]byte ("finalized" ), 32 ),
557
+ Epoch : 103 ,
558
+ }
559
+ })
560
+ require .NoError (t , err )
561
+ require .NoError (t , db .SaveState (ctx , st , r ))
562
+
563
+ s := Server {
564
+ BeaconDB : db ,
565
+ }
566
+
567
+ resp , err := s .GetFinalityCheckpoints (ctx , & ethpb.StateRequest {
568
+ StateId : []byte ("genesis" ),
569
+ })
570
+ require .NoError (t , err )
571
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("previous" ), 32 ), resp .Data .PreviousJustified .Root )
572
+ assert .Equal (t , types .Epoch (113 ), resp .Data .PreviousJustified .Epoch )
573
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("current" ), 32 ), resp .Data .CurrentJustified .Root )
574
+ assert .Equal (t , types .Epoch (123 ), resp .Data .CurrentJustified .Epoch )
575
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("finalized" ), 32 ), resp .Data .Finalized .Root )
576
+ assert .Equal (t , types .Epoch (103 ), resp .Data .Finalized .Epoch )
577
+ })
578
+
579
+ t .Run ("Finalized" , func (t * testing.T ) {
580
+ stateGen := stategen .NewMockService ()
581
+ stateGen .StatesByRoot [stateRoot ] = state
582
+
583
+ s := Server {
584
+ ChainInfoFetcher : & chainMock.ChainService {
585
+ FinalizedCheckPoint : & eth.Checkpoint {
586
+ Root : stateRoot [:],
587
+ },
588
+ },
589
+ StateGenService : stateGen ,
590
+ }
591
+
592
+ resp , err := s .GetFinalityCheckpoints (ctx , & ethpb.StateRequest {
593
+ StateId : []byte ("finalized" ),
594
+ })
595
+ require .NoError (t , err )
596
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("previous" ), 32 ), resp .Data .PreviousJustified .Root )
597
+ assert .Equal (t , types .Epoch (113 ), resp .Data .PreviousJustified .Epoch )
598
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("current" ), 32 ), resp .Data .CurrentJustified .Root )
599
+ assert .Equal (t , types .Epoch (123 ), resp .Data .CurrentJustified .Epoch )
600
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("finalized" ), 32 ), resp .Data .Finalized .Root )
601
+ assert .Equal (t , types .Epoch (103 ), resp .Data .Finalized .Epoch )
602
+ })
603
+
604
+ t .Run ("Justified" , func (t * testing.T ) {
605
+ stateGen := stategen .NewMockService ()
606
+ stateGen .StatesByRoot [stateRoot ] = state
607
+
608
+ s := Server {
609
+ ChainInfoFetcher : & chainMock.ChainService {
610
+ CurrentJustifiedCheckPoint : & eth.Checkpoint {
611
+ Root : stateRoot [:],
612
+ },
613
+ },
614
+ StateGenService : stateGen ,
615
+ }
616
+
617
+ resp , err := s .GetFinalityCheckpoints (ctx , & ethpb.StateRequest {
618
+ StateId : []byte ("justified" ),
619
+ })
620
+ require .NoError (t , err )
621
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("previous" ), 32 ), resp .Data .PreviousJustified .Root )
622
+ assert .Equal (t , types .Epoch (113 ), resp .Data .PreviousJustified .Epoch )
623
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("current" ), 32 ), resp .Data .CurrentJustified .Root )
624
+ assert .Equal (t , types .Epoch (123 ), resp .Data .CurrentJustified .Epoch )
625
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("finalized" ), 32 ), resp .Data .Finalized .Root )
626
+ assert .Equal (t , types .Epoch (103 ), resp .Data .Finalized .Epoch )
627
+ })
628
+
629
+ t .Run ("Hex root" , func (t * testing.T ) {
630
+ stateId , err := hexutil .Decode ("0x" + strings .Repeat ("0" , 63 ) + "1" )
631
+ require .NoError (t , err )
632
+ stateGen := stategen .NewMockService ()
633
+ stateGen .StatesByRoot [bytesutil .ToBytes32 (stateId )] = state
634
+
635
+ s := Server {
636
+ ChainInfoFetcher : & chainMock.ChainService {State : state },
637
+ StateGenService : stateGen ,
638
+ }
639
+
640
+ resp , err := s .GetFinalityCheckpoints (ctx , & ethpb.StateRequest {
641
+ StateId : stateId ,
642
+ })
643
+ require .NoError (t , err )
644
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("previous" ), 32 ), resp .Data .PreviousJustified .Root )
645
+ assert .Equal (t , types .Epoch (113 ), resp .Data .PreviousJustified .Epoch )
646
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("current" ), 32 ), resp .Data .CurrentJustified .Root )
647
+ assert .Equal (t , types .Epoch (123 ), resp .Data .CurrentJustified .Epoch )
648
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("finalized" ), 32 ), resp .Data .Finalized .Root )
649
+ assert .Equal (t , types .Epoch (103 ), resp .Data .Finalized .Epoch )
650
+ })
651
+
652
+ t .Run ("Hex root not found" , func (t * testing.T ) {
653
+ s := Server {
654
+ ChainInfoFetcher : & chainMock.ChainService {State : state },
655
+ }
656
+ stateId , err := hexutil .Decode ("0x" + strings .Repeat ("f" , 64 ))
657
+ require .NoError (t , err )
658
+ _ , err = s .GetFinalityCheckpoints (ctx , & ethpb.StateRequest {
659
+ StateId : stateId ,
660
+ })
661
+ require .ErrorContains (t , "State not found in the last 8192 state roots in head state" , err )
662
+ })
663
+
664
+ t .Run ("Slot" , func (t * testing.T ) {
665
+ stateGen := stategen .NewMockService ()
666
+ stateGen .StatesBySlot [headSlot ] = state
667
+
668
+ s := Server {
669
+ GenesisTimeFetcher : & chainMock.ChainService {Slot : & headSlot },
670
+ StateGenService : stateGen ,
671
+ }
672
+
673
+ resp , err := s .GetFinalityCheckpoints (ctx , & ethpb.StateRequest {
674
+ StateId : []byte (strconv .FormatUint (uint64 (headSlot ), 10 )),
675
+ })
676
+ require .NoError (t , err )
677
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("previous" ), 32 ), resp .Data .PreviousJustified .Root )
678
+ assert .Equal (t , types .Epoch (113 ), resp .Data .PreviousJustified .Epoch )
679
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("current" ), 32 ), resp .Data .CurrentJustified .Root )
680
+ assert .Equal (t , types .Epoch (123 ), resp .Data .CurrentJustified .Epoch )
681
+ assert .DeepEqual (t , bytesutil .PadTo ([]byte ("finalized" ), 32 ), resp .Data .Finalized .Root )
682
+ assert .Equal (t , types .Epoch (103 ), resp .Data .Finalized .Epoch )
683
+ })
684
+
685
+ t .Run ("Slot too big" , func (t * testing.T ) {
686
+ s := Server {
687
+ GenesisTimeFetcher : & chainMock.ChainService {
688
+ Genesis : time .Now (),
689
+ },
690
+ }
691
+ _ , err := s .GetFinalityCheckpoints (ctx , & ethpb.StateRequest {
692
+ StateId : []byte (strconv .FormatUint (1 , 10 )),
693
+ })
694
+ assert .ErrorContains (t , "Slot cannot be in the future" , err )
695
+ })
696
+
697
+ t .Run ("Checkpoints not available" , func (t * testing.T ) {
698
+ st , err := testutil .NewBeaconState ()
699
+ require .NoError (t , err )
700
+ err = st .SetPreviousJustifiedCheckpoint (nil )
701
+ require .NoError (t , err )
702
+ err = st .SetCurrentJustifiedCheckpoint (nil )
703
+ require .NoError (t , err )
704
+ err = st .SetFinalizedCheckpoint (nil )
705
+ require .NoError (t , err )
706
+
707
+ s := Server {
708
+ ChainInfoFetcher : & chainMock.ChainService {State : st },
709
+ }
710
+
711
+ resp , err := s .GetFinalityCheckpoints (ctx , & ethpb.StateRequest {
712
+ StateId : []byte ("head" ),
713
+ })
714
+ require .NoError (t , err )
715
+ assert .DeepEqual (t , params .BeaconConfig ().ZeroHash [:], resp .Data .PreviousJustified .Root )
716
+ assert .Equal (t , types .Epoch (0 ), resp .Data .PreviousJustified .Epoch )
717
+ assert .DeepEqual (t , params .BeaconConfig ().ZeroHash [:], resp .Data .CurrentJustified .Root )
718
+ assert .Equal (t , types .Epoch (0 ), resp .Data .CurrentJustified .Epoch )
719
+ assert .DeepEqual (t , params .BeaconConfig ().ZeroHash [:], resp .Data .Finalized .Root )
720
+ assert .Equal (t , types .Epoch (0 ), resp .Data .Finalized .Epoch )
721
+ })
722
+
723
+ t .Run ("Invalid state" , func (t * testing.T ) {
724
+ s := Server {}
725
+ _ , err := s .GetFinalityCheckpoints (ctx , & ethpb.StateRequest {
726
+ StateId : []byte ("foo" ),
727
+ })
728
+ require .ErrorContains (t , "Invalid state ID: foo" , err )
729
+ })
730
+ }
0 commit comments