Skip to content

Commit cf613dd

Browse files
remoteramienzo-bitfly
authored andcommitted
test: detailed rewards chart tests added
1 parent fd8c103 commit cf613dd

File tree

2 files changed

+63
-18
lines changed

2 files changed

+63
-18
lines changed

backend/pkg/api/api_test.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/json"
88
"flag"
99
"fmt"
10+
"math/big"
1011
"net/http"
1112
"net/http/cookiejar"
1213
"net/http/httptest"
@@ -30,6 +31,7 @@ import (
3031
"github.com/gobitfly/beaconchain/pkg/commons/version"
3132
"github.com/jmoiron/sqlx"
3233
"github.com/pressly/goose/v3"
34+
"github.com/shopspring/decimal"
3335
"github.com/stretchr/testify/assert"
3436
"github.com/stretchr/testify/require"
3537
"golang.org/x/crypto/bcrypt"
@@ -71,6 +73,24 @@ func teardown() {
7173
}
7274
}
7375

76+
type User struct {
77+
Email string `db:"email"`
78+
Password string
79+
ApiKey string `db:"api_key"`
80+
// optional
81+
Id uint `db:"id" goqu:"omitempty"`
82+
UserGroup string `db:"user_group" goqu:"omitempty"`
83+
EmailConfirmed bool `db:"email_confirmed" goqu:"omitempty"`
84+
}
85+
86+
var testUsers = []User{
87+
{Email: "admin@admin.com", Password: "admin", ApiKey: "admin", UserGroup: api_types.UserGroupAdmin, EmailConfirmed: true},
88+
// holesky
89+
{Id: 122558, Email: "admin2@admin.com", Password: "admin", ApiKey: "admin2", UserGroup: api_types.UserGroupAdmin, EmailConfirmed: true},
90+
{Id: 14, Email: "default@admin.com", Password: "default", ApiKey: "default", EmailConfirmed: true},
91+
{Id: 113321, Email: "admin3@admin.com", Password: "admin", ApiKey: "admin3", UserGroup: api_types.UserGroupAdmin, EmailConfirmed: true},
92+
}
93+
7494
func setup() error {
7595
configPath := flag.String("config", "", "Path to the config file, if empty string defaults will be used")
7696
flag.Parse()
@@ -287,7 +307,7 @@ func TestInternalLoginHandler(t *testing.T) {
287307
Decode(&meResponse)
288308

289309
// check if email is censored
290-
assert.Equal(t, meResponse.Data.Email, "a***n@a***n.com", "email should be a***n@a***n.com")
310+
assert.Equal(t, "a***n@a***n.com", meResponse.Data.Email, "email should be a***n@a***n.com")
291311
})
292312

293313
t.Run("check if logout works", func(t *testing.T) {
@@ -522,6 +542,8 @@ func TestPublicAndSharedDashboards(t *testing.T) {
522542
t.Run(fmt.Sprintf("[%s]: test rewards chart", dashboardId.id), func(t *testing.T) {
523543
resp := api_types.GetValidatorDashboardRewardsChartResponse{}
524544
e.GET("/api/i/validator-dashboards/{id}/rewards-chart", dashboardId.id).
545+
WithQuery("group_ids", "-1").
546+
WithQuery("aggregation", "weekly").
525547
Expect().Status(http.StatusOK).JSON().Decode(&resp)
526548

527549
assert.Greater(t, len(resp.Data.Categories), 0, "rewards chart categories should not be empty")

backend/pkg/api/data_access/vdb_rewards.go

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"sort"
1111
"strconv"
1212
"strings"
13+
"time"
1314

1415
"github.com/doug-martin/goqu/v9"
1516
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
@@ -696,11 +697,11 @@ func (d *DataAccessService) GetValidatorDashboardRewardsChart(ctx context.Contex
696697
var err error
697698

698699
var dataTable exp.LiteralExpression
699-
dataColumn := goqu.C("t")
700+
timeColumn := goqu.C("t")
700701
switch aggregation {
701702
case enums.IntervalEpoch:
702703
dataTable = goqu.L("validator_dashboard_data_epoch AS e")
703-
dataColumn = goqu.C("epoch_timestamp")
704+
timeColumn = goqu.C("epoch_timestamp")
704705
case enums.IntervalHourly:
705706
dataTable = goqu.L("validator_dashboard_data_hourly AS e FINAL")
706707
case enums.IntervalDaily:
@@ -724,7 +725,10 @@ func (d *DataAccessService) GetValidatorDashboardRewardsChart(ctx context.Contex
724725
// Build the query that serves as base for both the main and EL rewards queries
725726
// CL
726727
rewardsDs := goqu.Dialect("postgres").
727-
Select(goqu.L(`SUM(COALESCE(e.attestations_reward, 0) + COALESCE(e.blocks_cl_reward, 0) + COALESCE(e.sync_reward, 0)) AS cl_rewards`)).
728+
Select(
729+
goqu.L(`SUM(COALESCE(e.attestations_reward, 0) + COALESCE(e.blocks_cl_reward, 0) + COALESCE(e.sync_reward, 0)) AS cl_rewards`),
730+
timeColumn.As("timestamp"),
731+
).
728732
From(dataTable).
729733
With("validators", goqu.Dialect("postgres").
730734
From(goqu.T("users_val_dashboards_validators")).
@@ -741,19 +745,19 @@ func (d *DataAccessService) GetValidatorDashboardRewardsChart(ctx context.Contex
741745
),
742746
).
743747
Where(
744-
dataColumn.Between(goqu.Range(
748+
timeColumn.Between(goqu.Range(
745749
goqu.L("fromUnixTimestamp(?)", afterTs),
746750
goqu.L("fromUnixTimestamp(?)", beforeTs))),
747751
)
748752

749753
if aggregation == enums.IntervalEpoch {
750754
rewardsDs = rewardsDs.
751-
SelectAppend(goqu.L("e.epoch").As("epoch_start")).
752-
SelectAppend(goqu.L("e.epoch").As("epoch_end"))
755+
SelectAppend(goqu.L("min(e.epoch)").As("epoch_start")).
756+
SelectAppend(goqu.L("max(e.epoch)").As("epoch_end"))
753757
} else {
754758
rewardsDs = rewardsDs.
755-
SelectAppend(goqu.L("epoch_start")).
756-
SelectAppend(goqu.L("epoch_end"))
759+
SelectAppend(goqu.L("min(epoch_start)").As("epoch_start")).
760+
SelectAppend(goqu.L("max(epoch_end)").As("epoch_end"))
757761
}
758762

759763
// EL
@@ -767,8 +771,8 @@ func (d *DataAccessService) GetValidatorDashboardRewardsChart(ctx context.Contex
767771

768772
// grouping, ordering
769773
rewardsDs = rewardsDs.
770-
GroupBy(goqu.L("epoch_start, epoch_end")).
771-
Order(goqu.L("epoch_start").Asc())
774+
GroupBy(timeColumn).
775+
Order(timeColumn.Asc())
772776

773777
elDs = elDs.
774778
GroupBy(goqu.L("epoch_start, epoch_end")).
@@ -809,10 +813,11 @@ func (d *DataAccessService) GetValidatorDashboardRewardsChart(ctx context.Contex
809813
// ------------------------------------------------------------------------------------------------------------------
810814
// Build the main query and get the data
811815
queryResult := []struct {
812-
EpochStart uint64 `db:"epoch_start"`
813-
EpochEnd uint64 `db:"epoch_end"`
814-
GroupId uint64 `db:"result_group_id"`
815-
ClRewards int64 `db:"cl_rewards"`
816+
Timestamp time.Time `db:"timestamp"`
817+
EpochStart uint64 `db:"epoch_start"`
818+
EpochEnd uint64 `db:"epoch_end"`
819+
GroupId uint64 `db:"result_group_id"`
820+
ClRewards int64 `db:"cl_rewards"`
816821
}{}
817822

818823
query, args, err := rewardsDs.Prepared(true).ToSQL()
@@ -829,11 +834,29 @@ func (d *DataAccessService) GetValidatorDashboardRewardsChart(ctx context.Contex
829834
return ret, nil
830835
}
831836

832-
var epochStarts, epochEnds []uint64
837+
// deduplicate epoch boundaries & make sure they are correct even with newly activated validators
838+
type epochBoundaries struct {
839+
Start uint64
840+
End uint64
841+
}
842+
epochBoundariesMap := make(map[time.Time]epochBoundaries)
833843
for _, res := range queryResult {
834-
epochStarts = append(epochStarts, res.EpochStart)
835-
epochEnds = append(epochEnds, res.EpochEnd)
844+
curBoundary := epochBoundariesMap[res.Timestamp]
845+
if epochBoundariesMap[res.Timestamp].Start == 0 || epochBoundariesMap[res.Timestamp].Start > res.EpochStart {
846+
curBoundary.Start = res.EpochStart
847+
}
848+
if epochBoundariesMap[res.Timestamp].End < res.EpochEnd {
849+
curBoundary.End = res.EpochEnd
850+
}
851+
epochBoundariesMap[res.Timestamp] = curBoundary
852+
}
853+
var epochStarts, epochEnds []uint64
854+
for _, v := range epochBoundariesMap {
855+
epochStarts = append(epochStarts, v.Start)
856+
epochEnds = append(epochEnds, v.End)
836857
}
858+
slices.Sort(epochStarts)
859+
slices.Sort(epochEnds)
837860
elDs = elDs.
838861
With("epoch_ranges(epoch_start, epoch_end)", goqu.L("(SELECT * FROM unnest(?::int[], ?::int[]))", pq.Array(epochStarts), pq.Array(epochEnds))).
839862
InnerJoin(goqu.L("epoch_ranges"), goqu.On(goqu.L("b.epoch BETWEEN epoch_ranges.epoch_start AND epoch_ranges.epoch_end"))).

0 commit comments

Comments
 (0)