@@ -472,3 +472,259 @@ func TestGetStateFork(t *testing.T) {
472472 require .ErrorContains (t , "Invalid state ID: foo" , err )
473473 })
474474}
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