Skip to content

Commit f8b222f

Browse files
committed
tests(data_access): refactoring GetValidatorDashboardGroupSummary and adding unit tests
1 parent a317ec9 commit f8b222f

File tree

3 files changed

+441
-254
lines changed

3 files changed

+441
-254
lines changed

backend/pkg/api/data_access/vdb_helpers.go

Lines changed: 167 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ type SyncCommitteeResult struct {
440440
// to determine validators current and upcoming sync committees
441441
func (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
531696
func processPastSyncCommitteesResults(validatorIndices []uint64) (map[uint64]uint64, error) {
532697
validatorCountMap := make(map[uint64]uint64)

0 commit comments

Comments
 (0)