@@ -440,7 +440,7 @@ type SyncCommitteeResult struct {
440440// to determine validators current and upcoming sync committees
441441func (d * DataAccessService ) getCurrentAndUpcomingSyncCommittees (ctx context.Context , latestEpoch uint64 ) (map [uint64 ]bool , map [uint64 ]bool , error ) {
442442 currentSyncPeriod := utils .SyncPeriodOfEpoch (latestEpoch )
443- ds := buildSyncCommitteeQuery (currentSyncPeriod )
443+ ds := buildCurrentAndUpcomingSyncCommitteesQuery (currentSyncPeriod )
444444 queryResult , err := runQueryRows [[]SyncCommitteeResult ](ctx , d .readerDb , ds )
445445 if err != nil {
446446 return nil , nil , err
@@ -449,7 +449,7 @@ func (d *DataAccessService) getCurrentAndUpcomingSyncCommittees(ctx context.Cont
449449}
450450
451451// Builds a query to fetch sync committee validators for the current and next period
452- func buildSyncCommitteeQuery (currentSyncPeriod uint64 ) * goqu.SelectDataset {
452+ func buildCurrentAndUpcomingSyncCommitteesQuery (currentSyncPeriod uint64 ) * goqu.SelectDataset {
453453 return goqu .Dialect ("postgres" ).
454454 Select (
455455 goqu .L ("validatorindex" ),
@@ -527,6 +527,171 @@ func buildPastSyncCommitteesQuery(indices []uint64, pastSyncPeriodCutoff, curren
527527 Where (goqu .L ("period >= ? AND period < ? AND validatorindex = ANY(?)" , pastSyncPeriodCutoff , currentSyncPeriod , pq .Array (indices )))
528528}
529529
530+ // EpochRange represents the result structure for min and max epochs
531+ type EpochRange struct {
532+ MinEpochStart * uint64 `db:"min_epoch_start"`
533+ MaxEpochEnd * uint64 `db:"max_epoch_end"`
534+ }
535+
536+ // Constructs the SQL query for retrieving min/max epochs
537+ func buildMinMaxEpochsQuery (dashboardId t.VDBId , groupId int64 , clickhouseTable string ) * goqu.SelectDataset {
538+ ds := goqu .Dialect ("postgres" ).
539+ Select (
540+ goqu .L ("MIN(epoch_start) as min_epoch_start" ),
541+ goqu .L ("MAX(epoch_end) as max_epoch_end" )).
542+ From (goqu .L (fmt .Sprintf (`%s AS r FINAL` , clickhouseTable )))
543+
544+ if dashboardId .Validators == nil {
545+ ds = ds .
546+ With ("validators" , goqu .L ("(SELECT validator_index as validator_index, group_id FROM users_val_dashboards_validators WHERE dashboard_id = ? AND (group_id = ? OR ?::smallint = -1))" , dashboardId .Id , groupId , groupId )).
547+ InnerJoin (goqu .L ("validators v" ), goqu .On (goqu .L ("r.validator_index = v.validator_index" ))).
548+ Where (goqu .L ("validator_index IN (SELECT validator_index FROM validators)" ))
549+ } else {
550+ ds = ds .
551+ Where (goqu .L ("validator_index IN ?" , dashboardId .Validators ))
552+ }
553+ return ds
554+ }
555+
556+ // GetMinMaxEpochs is the main function that ties all steps together
557+ func (d * DataAccessService ) getMinMaxEpochs (ctx context.Context , dashboardId t.VDBId , groupId int64 , period enums.TimePeriod ) (uint64 , uint64 , error ) {
558+ clickhouseTable , _ , err := getTablesForPeriod (period )
559+ if err != nil {
560+ return 0 , 0 , err
561+ }
562+
563+ ds := buildMinMaxEpochsQuery (dashboardId , groupId , clickhouseTable )
564+
565+ epochRow , err := runQuery [EpochRange ](ctx , d .clickhouseReader , ds )
566+ if err != nil {
567+ return 0 , 0 , err
568+ }
569+
570+ return * epochRow .MinEpochStart , * epochRow .MaxEpochEnd , nil
571+ }
572+
573+ type LastScheduledEpoch struct {
574+ LastScheduledBlockEpoch * int64 `db:"last_scheduled_block_epoch"`
575+ LastSyncEpoch * int64 `db:"last_scheduled_sync_epoch"`
576+ }
577+
578+ // Constructs the SQL query for retrieving last scheduled block and sync epochs
579+ func buildLastScheduledBlockAndSyncDateQuery (clickhouseTable string , dashboardId t.VDBId , groupId int64 ) * goqu.SelectDataset {
580+ ds := goqu .Dialect ("postgres" ).
581+ Select (
582+ goqu .L ("MAX(last_scheduled_block_epoch) as last_scheduled_block_epoch" ),
583+ goqu .L ("MAX(last_scheduled_sync_epoch) as last_scheduled_sync_epoch" )).
584+ From (goqu .L (fmt .Sprintf (`%s AS r FINAL` , clickhouseTable )))
585+
586+ if dashboardId .Validators != nil {
587+ // If Validators are provided, use them directly in the WHERE clause
588+ return ds .Where (goqu .L ("validator_index IN ?" , dashboardId .Validators ))
589+ }
590+
591+ // If Validators are nil, use a subquery
592+ return ds .
593+ With ("validators" , goqu .L ("(SELECT validator_index, group_id FROM users_val_dashboards_validators WHERE dashboard_id = ? AND (group_id = ? OR ?::smallint = -1))" , dashboardId .Id , groupId , groupId )).
594+ InnerJoin (goqu .L ("validators v" ), goqu .On (goqu .L ("r.validator_index = v.validator_index" ))).
595+ Where (goqu .L ("validator_index IN (SELECT validator_index FROM validators)" ))
596+ }
597+
598+ // Gets last scheduled block/sync committee epoch
599+ func (d * DataAccessService ) getLastScheduledBlockAndSyncDate (ctx context.Context , dashboardId t.VDBId , groupId int64 ) (time.Time , time.Time , error ) {
600+ // we need to use clickhouse table for all_time period
601+ clickhouseTotalTable , _ , err := getTablesForPeriod (enums .AllTime )
602+ if err != nil {
603+ return time.Time {}, time.Time {}, err
604+ }
605+
606+ ds := buildLastScheduledBlockAndSyncDateQuery (clickhouseTotalTable , dashboardId , groupId )
607+
608+ epochRow , err := runQuery [LastScheduledEpoch ](ctx , d .clickhouseReader , ds )
609+ if err != nil {
610+ return time.Time {}, time.Time {}, err
611+ } else if epochRow .LastScheduledBlockEpoch == nil || epochRow .LastSyncEpoch == nil {
612+ return time.Time {}, time.Time {}, nil
613+ } else {
614+ return utils .EpochToTime (uint64 (* epochRow .LastScheduledBlockEpoch )),
615+ utils .EpochToTime (uint64 (* epochRow .LastSyncEpoch )),
616+ nil
617+ }
618+ }
619+
620+ // Builds a query to calculate the total missed EL rewards for a given epoch range
621+ func buildMissedELRewardsQuery (dashboardId t.VDBId , groupId int64 , slots , epochStart , epochEnd uint64 ) * goqu.SelectDataset {
622+ // Define the `targets` CTE
623+ targets := goqu .Dialect ("postgres" ).
624+ From ("blocks" ).
625+ Select (goqu .I ("blocks.slot" ).As ("slot" )).
626+ Where (
627+ goqu .I ("blocks.status" ).Neq ("1" ),
628+ goqu .I ("epoch" ).Gte (epochStart ),
629+ goqu .I ("epoch" ).Lte (epochEnd ),
630+ )
631+
632+ // Filter validators
633+ if dashboardId .Validators == nil {
634+ targets = targets .
635+ Join (
636+ goqu .T ("users_val_dashboards_validators" ).As ("uvdv" ),
637+ goqu .On (goqu .I ("blocks.proposer" ).Eq (goqu .I ("uvdv.validator_index" ))),
638+ ).
639+ Where (
640+ goqu .And (
641+ goqu .I ("uvdv.dashboard_id" ).Eq (dashboardId .Id ),
642+ goqu .Or (
643+ goqu .I ("uvdv.group_id" ).Eq (groupId ),
644+ goqu .L ("?::smallint = -1" , groupId ),
645+ ),
646+ ),
647+ )
648+ } else {
649+ targets = targets .
650+ Where (
651+ goqu .I ("blocks.proposer" ).In (dashboardId .Validators ),
652+ )
653+ }
654+
655+ // Define the res CTE
656+ res := goqu .
657+ From ("targets" ).
658+ LeftJoin (
659+ goqu .T ("execution_rewards_finalized" ).As ("b" ),
660+ goqu .On (
661+ goqu .L (fmt .Sprintf (
662+ `"b"."slot" >= "targets"."slot" - %d AND "b"."slot" < "targets"."slot" + %d` ,
663+ slots , slots ,
664+ )),
665+ ),
666+ ).
667+ Select (
668+ goqu .I ("targets.slot" ),
669+ goqu .L ("percentile_cont(0.5) WITHIN GROUP (ORDER BY b.value)::numeric(76,0)" ).As ("v" ),
670+ ).
671+ GroupBy (goqu .I ("targets.slot" ))
672+
673+ // Build the final query
674+ query := goqu .From ("res" ).
675+ With ("targets" , targets ).
676+ With ("res" , res ).
677+ Select (goqu .L ("COALESCE(SUM(v), 0)" ))
678+
679+ return query
680+ }
681+
682+ // Get total median EL rewards
683+ func (d * DataAccessService ) getMissedELRewards (ctx context.Context , dashboardId t.VDBId , groupId int64 , epochStart , epochEnd uint64 ) (float64 , error ) {
684+ slots := utils .Config .Chain .ClConfig .SlotsPerEpoch / 2
685+ query := buildMissedELRewardsQuery (dashboardId , groupId , slots , epochStart , epochEnd )
686+
687+ totalMissedRewardsEl , err := runQuery [float64 ](ctx , d .readerDb , query )
688+ if err != nil {
689+ return 0 , err
690+ }
691+
692+ return totalMissedRewardsEl , nil
693+ }
694+
530695// Processes past sync committee results by counting occurrences of each validator
531696func processPastSyncCommitteesResults (validatorIndices []uint64 ) (map [uint64 ]uint64 , error ) {
532697 validatorCountMap := make (map [uint64 ]uint64 )
0 commit comments