@@ -683,25 +683,15 @@ func (d *DataAccessService) GetValidatorDashboardGroupRewards(ctx context.Contex
683683 return ret , nil
684684}
685685
686- func (d * DataAccessService ) GetValidatorDashboardRewardsChart (ctx context.Context , dashboardId t.VDBId , groupIds []int64 , protocolModes t.VDBProtocolModes , aggregation enums.ChartAggregation , afterTs uint64 , beforeTs uint64 ) (* t.ChartData [int , decimal.Decimal ], error ) {
687- // @DATA-ACCESS incorporate protocolModes
688- // bar chart for the CL and EL rewards for each group for each epoch.
689- // NO series for all groups combined except if AggregateGroups is true.
690- // series id is group id, series property is 'cl' or 'el'
691- ret := & t.ChartData [int , decimal.Decimal ]{}
692-
693- if len (groupIds ) == 0 {
694- return ret , nil
695- }
696-
697- var err error
698-
686+ func buildRewardChartClDs (dashboardId t.VDBId , groupIds []int64 , afterTs uint64 , beforeTs uint64 , aggregation enums.ChartAggregation , isAllGroupsRequested bool ) (* goqu.SelectDataset , error ) {
699687 var dataTable exp.LiteralExpression
700688 timeColumn := goqu .C ("t" )
689+ epochStartCol , epochEndCol := "epoch_start" , "epoch_end"
701690 switch aggregation {
702691 case enums .IntervalEpoch :
703692 dataTable = goqu .L ("validator_dashboard_data_epoch AS e" )
704693 timeColumn = goqu .C ("epoch_timestamp" )
694+ epochStartCol , epochEndCol = "e.epoch" , "e.epoch"
705695 case enums .IntervalHourly :
706696 dataTable = goqu .L ("validator_dashboard_data_hourly AS e FINAL" )
707697 case enums .IntervalDaily :
@@ -712,22 +702,12 @@ func (d *DataAccessService) GetValidatorDashboardRewardsChart(ctx context.Contex
712702 return nil , fmt .Errorf ("unexpected aggregation type: %v" , aggregation )
713703 }
714704
715- requestedAllGroups := dashboardId .AggregateGroups
716- for _ , groupId := range groupIds {
717- if groupId == t .AllGroups {
718- // note: requesting all groups is only convenience on api level, this will NOT result in a "total" series as it wouldn't make sense for this endpoint
719- requestedAllGroups = true
720- break
721- }
722- }
723-
724- // ------------------------------------------------------------------------------------------------------------------
725- // Build the query that serves as base for both the main and EL rewards queries
726- // CL
727- rewardsDs := goqu .Dialect ("postgres" ).
705+ clDs := goqu .Dialect ("postgres" ).
728706 Select (
729707 goqu .L (`SUM(e.attestations_reward + e.blocks_cl_reward + e.sync_reward) AS cl_rewards` ),
730708 timeColumn .As ("timestamp" ),
709+ goqu .L (fmt .Sprintf ("min(%s)" , epochStartCol )).As ("epoch_start" ),
710+ goqu .L (fmt .Sprintf ("max(%s)" , epochEndCol )).As ("epoch_end" ),
731711 ).
732712 From (dataTable ).
733713 With ("validators" , goqu .Dialect ("postgres" ).
@@ -740,94 +720,108 @@ func (d *DataAccessService) GetValidatorDashboardRewardsChart(ctx context.Contex
740720 goqu .I ("dashboard_id" ).Eq (dashboardId .Id ),
741721 goqu .Or (
742722 goqu .I ("group_id" ).In (groupIds ),
743- goqu .V (requestedAllGroups ),
723+ goqu .V (isAllGroupsRequested ),
744724 ),
745725 ),
746726 ).
747727 Where (
748728 timeColumn .Between (goqu .Range (
749729 goqu .L ("fromUnixTimestamp(?)" , afterTs ),
750730 goqu .L ("fromUnixTimestamp(?)" , beforeTs ))),
751- )
731+ ).
732+ GroupBy (timeColumn ).
733+ Order (timeColumn .Asc ())
752734
753- if aggregation == enums .IntervalEpoch {
754- rewardsDs = rewardsDs .
755- SelectAppend (goqu .L ("min(e.epoch)" ).As ("epoch_start" )).
756- SelectAppend (goqu .L ("max(e.epoch)" ).As ("epoch_end" ))
735+ if dashboardId .Validators == nil {
736+ clDs = clDs .
737+ InnerJoin (goqu .L ("validators v" ), goqu .On (goqu .L ("e.validator_index = v.validator_index" ))).
738+ Where (goqu .L ("e.validator_index IN (SELECT validator_index FROM validators)" ))
739+
740+ if dashboardId .AggregateGroups {
741+ clDs = clDs .
742+ SelectAppend (goqu .L ("?::smallint AS result_group_id" , t .DefaultGroupId ))
743+ } else {
744+ clDs = clDs .
745+ SelectAppend (goqu .L ("v.group_id AS result_group_id" )).
746+ GroupByAppend (goqu .L ("result_group_id" )).
747+ OrderAppend (goqu .L ("result_group_id" ).Asc ())
748+ }
757749 } else {
758- rewardsDs = rewardsDs .
759- SelectAppend (goqu .L ("min(epoch_start)" ).As ("epoch_start" )).
760- SelectAppend (goqu .L ("max(epoch_end)" ).As ("epoch_end" ))
750+ // In case a list of validators is provided set the group to the default id
751+ clDs = clDs .
752+ SelectAppend (goqu .L ("?::smallint AS result_group_id" , t .DefaultGroupId )).
753+ Where (goqu .L ("e.validator_index IN ?" , dashboardId .Validators ))
761754 }
755+ return clDs , nil
756+ }
762757
763- // EL
758+ func buildRewardChartElDs ( dashboardId t. VDBId , epochStarts , epochEnds [] uint64 ) * goqu. SelectDataset {
764759 elDs := goqu .Dialect ("postgres" ).
765760 Select (
766761 goqu .L ("epoch_start" ),
767762 // goqu.L("epoch_end"), not needed
768763 goqu .COALESCE (goqu .SUM (goqu .I ("value" )), 0 ).As ("el_rewards" )).
769764 From (goqu .L ("users_val_dashboards_validators v" )).
770- LeftJoin (goqu .I ("execution_rewards_finalized" ).As ("b" ), goqu .On (goqu .L ("v.validator_index = b.proposer" )))
771-
772- // grouping, ordering
773- rewardsDs = rewardsDs .
774- GroupBy (timeColumn ).
775- Order (timeColumn .Asc ())
776-
777- elDs = elDs .
765+ LeftJoin (goqu .I ("execution_rewards_finalized" ).As ("b" ), goqu .On (goqu .L ("v.validator_index = b.proposer" ))).
778766 GroupBy (goqu .L ("epoch_start, epoch_end" )).
779767 Order (goqu .L ("epoch_start" ).Asc ())
780768
781769 if dashboardId .Validators == nil {
782- rewardsDs = rewardsDs .
783- InnerJoin (goqu .L ("validators v" ), goqu .On (goqu .L ("e.validator_index = v.validator_index" ))).
784- Where (goqu .L ("e.validator_index IN (SELECT validator_index FROM validators)" ))
785770 elDs = elDs .
786771 Where (goqu .L ("v.dashboard_id = ?" , dashboardId .Id ))
787772
788773 if dashboardId .AggregateGroups {
789- rewardsDs = rewardsDs .
790- SelectAppend (goqu .L ("?::smallint AS result_group_id" , t .DefaultGroupId ))
791774 elDs = elDs .
792775 SelectAppend (goqu .L ("?::smallint AS result_group_id" , t .DefaultGroupId ))
793776 } else {
794- rewardsDs = rewardsDs .
795- SelectAppend (goqu .L ("v.group_id AS result_group_id" )).
796- GroupByAppend (goqu .L ("result_group_id" )).
797- OrderAppend (goqu .L ("result_group_id" ).Asc ())
798777 elDs = elDs .
799778 SelectAppend (goqu .L ("v.group_id AS result_group_id" )).
800779 GroupByAppend (goqu .L ("result_group_id" )).
801780 OrderAppend (goqu .L ("result_group_id" ).Asc ())
802781 }
803782 } else {
804783 // In case a list of validators is provided set the group to the default id
805- rewardsDs = rewardsDs .
806- SelectAppend (goqu .L ("?::smallint AS result_group_id" , t .DefaultGroupId )).
807- Where (goqu .L ("e.validator_index IN ?" , dashboardId .Validators ))
808784 elDs = elDs .
809785 SelectAppend (goqu .L ("?::smallint AS result_group_id" , t .DefaultGroupId )).
810786 Where (goqu .L ("b.proposer = ANY(?)" , pq .Array (dashboardId .Validators )))
811787 }
788+ elDs = elDs .
789+ With ("epoch_ranges(epoch_start, epoch_end)" , goqu .L ("(SELECT * FROM unnest(?::int[], ?::int[]))" , pq .Array (epochStarts ), pq .Array (epochEnds ))).
790+ InnerJoin (goqu .L ("epoch_ranges" ), goqu .On (goqu .L ("b.epoch BETWEEN epoch_ranges.epoch_start AND epoch_ranges.epoch_end" ))).
791+ Where (goqu .L ("b.epoch BETWEEN ? AND ?" , epochStarts [0 ], epochEnds [len (epochEnds )- 1 ]))
792+ return elDs
793+ }
812794
795+ func (d * DataAccessService ) GetValidatorDashboardRewardsChart (ctx context.Context , dashboardId t.VDBId , groupIds []int64 , protocolModes t.VDBProtocolModes , aggregation enums.ChartAggregation , afterTs uint64 , beforeTs uint64 ) (* t.ChartData [int , decimal.Decimal ], error ) {
796+ // @DATA-ACCESS incorporate protocolModes
797+ // bar chart for the CL and EL rewards for each group for each epoch.
798+ // NO series for all groups combined except if AggregateGroups is true.
799+ // series id is group id, series property is 'cl' or 'el'
800+ ret := & t.ChartData [int , decimal.Decimal ]{}
801+
802+ if len (groupIds ) == 0 {
803+ return ret , nil
804+ }
805+
806+ requestedAllGroups := dashboardId .AggregateGroups || slices .Contains (groupIds , t .AllGroups )
813807 // ------------------------------------------------------------------------------------------------------------------
814- // Build the main query and get the data
815- queryResult := []struct {
808+ // CL
809+
810+ clDs , err := buildRewardChartClDs (dashboardId , groupIds , afterTs , beforeTs , aggregation , requestedAllGroups )
811+ if err != nil {
812+ return nil , fmt .Errorf ("error building CL rewards query: %w" , err )
813+ }
814+ type clResult struct {
816815 Timestamp time.Time `db:"timestamp"`
817816 EpochStart uint64 `db:"epoch_start"`
818817 EpochEnd uint64 `db:"epoch_end"`
819818 GroupId uint64 `db:"result_group_id"`
820819 ClRewards int64 `db:"cl_rewards"`
821- }{}
822-
823- query , args , err := rewardsDs .Prepared (true ).ToSQL ()
824- if err != nil {
825- return nil , fmt .Errorf ("error preparing query: %w" , err )
826820 }
827821
828- err = d . clickhouseReader . SelectContext (ctx , & queryResult , query , args ... )
822+ queryResult , err := runQueryRows [[] clResult ] (ctx , d . clickhouseReader , clDs )
829823 if err != nil {
830- return nil , fmt .Errorf ("error retrieving rewards chart data: %w" , err )
824+ return nil , fmt .Errorf ("error retrieving rewards chart cl data: %w" , err )
831825 }
832826
833827 if len (queryResult ) == 0 {
@@ -842,12 +836,10 @@ func (d *DataAccessService) GetValidatorDashboardRewardsChart(ctx context.Contex
842836 epochBoundariesMap := make (map [time.Time ]epochBoundaries )
843837 for _ , res := range queryResult {
844838 curBoundary := epochBoundariesMap [res .Timestamp ]
845- if epochBoundariesMap [ res . Timestamp ]. Start == 0 || epochBoundariesMap [ res . Timestamp ] .Start > res .EpochStart {
839+ if curBoundary . Start == 0 || curBoundary .Start > res .EpochStart {
846840 curBoundary .Start = res .EpochStart
847841 }
848- if epochBoundariesMap [res .Timestamp ].End < res .EpochEnd {
849- curBoundary .End = res .EpochEnd
850- }
842+ curBoundary .End = max (curBoundary .End , res .EpochEnd )
851843 epochBoundariesMap [res .Timestamp ] = curBoundary
852844 }
853845 var epochStarts , epochEnds []uint64
@@ -857,89 +849,67 @@ func (d *DataAccessService) GetValidatorDashboardRewardsChart(ctx context.Contex
857849 }
858850 slices .Sort (epochStarts )
859851 slices .Sort (epochEnds )
860- elDs = elDs .
861- With ("epoch_ranges(epoch_start, epoch_end)" , goqu .L ("(SELECT * FROM unnest(?::int[], ?::int[]))" , pq .Array (epochStarts ), pq .Array (epochEnds ))).
862- InnerJoin (goqu .L ("epoch_ranges" ), goqu .On (goqu .L ("b.epoch BETWEEN epoch_ranges.epoch_start AND epoch_ranges.epoch_end" ))).
863- Where (goqu .L ("b.epoch BETWEEN ? AND ?" , epochStarts [0 ], epochEnds [len (epochEnds )- 1 ]))
864852
865853 // ------------------------------------------------------------------------------------------------------------------
866- // Get the EL rewards
867- elRewards := make (map [uint64 ]map [uint64 ]decimal.Decimal )
854+ // EL
868855
869- elQueryResult := [] struct {
856+ type elResult struct {
870857 EpochStart uint64 `db:"epoch_start"`
871858 GroupId uint64 `db:"result_group_id"`
872859 ElRewards decimal.Decimal `db:"el_rewards"`
873- }{}
874-
875- query , args , err = elDs .Prepared (true ).ToSQL ()
876- if err != nil {
877- return nil , fmt .Errorf ("error preparing query: %w" , err )
878860 }
879-
880- err = d .readerDb .SelectContext (ctx , & elQueryResult , query , args ... )
861+ elQueryResult , err := runQueryRows [[]elResult ](ctx , d .readerDb , buildRewardChartElDs (dashboardId , epochStarts , epochEnds ))
881862 if err != nil {
882- return nil , fmt .Errorf ("error retrieving el rewards data for rewards chart : %w" , err )
863+ return nil , fmt .Errorf ("error retrieving rewards chart el data : %w" , err )
883864 }
884865
885- for _ , entry := range elQueryResult {
886- if _ , ok := elRewards [entry .EpochStart ]; ! ok {
887- elRewards [entry .EpochStart ] = make (map [uint64 ]decimal.Decimal )
888- }
889- elRewards [entry.EpochStart ][entry.GroupId ] = entry .ElRewards
866+ type elRewardsKey struct {
867+ epochStart uint64
868+ groupId uint64
890869 }
891-
892- if err != nil {
893- return nil , fmt . Errorf ( "error retrieving validator dashboard rewards chart data: %w" , err )
870+ elRewards := make ( map [ elRewardsKey ]decimal. Decimal )
871+ for _ , entry := range elQueryResult {
872+ elRewards [ elRewardsKey { entry . EpochStart , entry . GroupId }] = entry . ElRewards
894873 }
895874
896875 // ------------------------------------------------------------------------------------------------------------------
897876 // Create a map structure to store the data
898- epochStartData := make (map [uint64 ]map [int ]t.ClElValue [decimal.Decimal ])
899- epochStartList := make ([]uint64 , 0 )
900- groupMap := make (map [int ]bool )
901877
902- for _ , res := range queryResult {
903- if _ , ok := epochStartData [res .EpochStart ]; ! ok {
904- epochStartData [res .EpochStart ] = make (map [int ]t.ClElValue [decimal.Decimal ])
905- epochStartList = append (epochStartList , res .EpochStart )
906- }
878+ epochStartData := make (map [elRewardsKey ]t.ClElValue [decimal.Decimal ])
879+ epochStartMap := make (map [uint64 ]struct {})
880+ groupMap := make (map [uint64 ]struct {})
907881
908- epochStartData [res .EpochStart ][int (res .GroupId )] = t.ClElValue [decimal.Decimal ]{
909- El : elRewards [res.EpochStart ][res.GroupId ],
882+ for _ , res := range queryResult {
883+ groupMap [res .GroupId ] = struct {}{}
884+ epochStartMap [res .EpochStart ] = struct {}{}
885+ epochStartData [elRewardsKey {res .EpochStart , res .GroupId }] = t.ClElValue [decimal.Decimal ]{
886+ El : elRewards [elRewardsKey {res .EpochStart , res .GroupId }],
910887 Cl : utils .GWeiToWei (big .NewInt (res .ClRewards )),
911888 }
912- groupMap [int (res .GroupId )] = true
913889 }
914-
915- // Get the list of groups
916- groupList := slices .Collect (maps .Keys (groupMap ))
917- slices .Sort (groupList )
918-
919- // Create the series structure
920- propertyNames := []string {"el" , "cl" }
921- for _ , groupId := range groupList {
922- for _ , propertyName := range propertyNames {
923- ret .Series = append (ret .Series , t.ChartSeries [int , decimal.Decimal ]{
924- Id : groupId ,
925- Property : propertyName ,
926- })
927- }
928- }
929-
930- // Fill the epoch data
890+ epochStartList := slices .Sorted (maps .Keys (epochStartMap ))
931891 for _ , epoch := range epochStartList {
932892 ret .Categories = append (ret .Categories , uint64 (utils .EpochToTime (epoch ).Unix ()))
933- for idx , series := range ret .Series {
934- d := epochStartData [epoch ][series.Id ]
935- if series .Property == "el" {
936- ret .Series [idx ].Data = append (ret .Series [idx ].Data , & d .El )
937- } else if series .Property == "cl" {
938- ret .Series [idx ].Data = append (ret .Series [idx ].Data , & d .Cl )
939- } else {
940- return nil , fmt .Errorf ("unknown series property: %s" , series .Property )
941- }
942- }
893+ }
894+ for _ , groupId := range slices .Sorted (maps .Keys (groupMap )) {
895+ var clData , elData []* decimal.Decimal
896+ for _ , epoch := range epochStartList {
897+ d := epochStartData [elRewardsKey {epoch , groupId }]
898+ clData = append (clData , & d .Cl )
899+ elData = append (elData , & d .El )
900+ }
901+ ret .Series = append (ret .Series ,
902+ t.ChartSeries [int , decimal.Decimal ]{
903+ Id : int (groupId ),
904+ Property : "cl" ,
905+ Data : clData ,
906+ },
907+ t.ChartSeries [int , decimal.Decimal ]{
908+ Id : int (groupId ),
909+ Property : "el" ,
910+ Data : elData ,
911+ },
912+ )
943913 }
944914
945915 return ret , nil
0 commit comments