Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 50 additions & 26 deletions backend/pkg/api/data_access/mobile.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ func (d *DataAccessService) GetValidatorDashboardMobileWidget(ctx context.Contex
if err != nil {
return nil, fmt.Errorf("error retrieving validator dashboard overview data: %w", err)
}
data.NetworkEfficiency = efficiency.TotalEfficiency[enums.AllTime].Float64
networkEfficiency := efficiency.TotalEfficiency[enums.AllTime].Float64
data.NetworkEfficiency = &networkEfficiency

// Validator status
eg.Go(func() error {
Expand Down Expand Up @@ -251,23 +252,31 @@ func (d *DataAccessService) GetValidatorDashboardMobileWidget(ctx context.Contex
share := queryResult.EffectiveRPLStake.Div(rpNetworkStats.EffectiveRPLStaked)

periodsPerYear := decimal.NewFromFloat(365 / (rpNetworkStats.ClaimIntervalHours / 24))
data.RplApr = rpNetworkStats.NodeOperatorRewards.
rplApr := rpNetworkStats.NodeOperatorRewards.
Mul(share).
Div(queryResult.RPLStake).
Mul(periodsPerYear).InexactFloat64()
data.RplApr = &rplApr
}
return nil
})

retrieveApr := func(timeFrame enums.TimePeriod, apr *float64) {
eg.Go(func() error {
incomeInfo, err := d.getElClAPR(ctx, wrappedDashboardId, -1, timeFrame)
if err != nil {
return err
}
*apr = incomeInfo.Apr.El + incomeInfo.Apr.Cl
return nil
})
retrieveApr := func(timeFrame enums.TimePeriod) (*float64, error) {
incomeInfo, err := d.getElClAPR(ctx, wrappedDashboardId, -1, timeFrame)
if err != nil {
return nil, err
}
if incomeInfo.Apr.El == nil && incomeInfo.Apr.Cl == nil {
return nil, nil
}
var totalApr float64
if incomeInfo.Apr.El != nil {
totalApr += *incomeInfo.Apr.El
}
if incomeInfo.Apr.Cl != nil {
totalApr += *incomeInfo.Apr.Cl
}
return &totalApr, nil
}

retrieveRewards := func(timeFrame enums.TimePeriod, rewards *t.ClElValue[decimal.Decimal]) {
Expand All @@ -281,27 +290,42 @@ func (d *DataAccessService) GetValidatorDashboardMobileWidget(ctx context.Contex
})
}

retrieveEfficiency := func(table string, efficiency *float64) {
eg.Go(func() error {
ds := goqu.Dialect("postgres").
From(goqu.L(fmt.Sprintf(`%s AS r`, table))).
With("validators", goqu.L("(SELECT dashboard_id, validator_index FROM users_val_dashboards_validators WHERE dashboard_id = ?)", dashboardId)).
Select(
goqu.L("COALESCE(SUM(efficiency_dividend::Int256) / NULLIF(SUM(efficiency_divisor::Int256), 0), 0)").As("efficiency"),
).
InnerJoin(goqu.L("validators v"), goqu.On(goqu.L("r.validator_index = v.validator_index"))).
Where(goqu.L("r.validator_index IN (SELECT validator_index FROM validators)"))
retrieveEfficiency := func(table string) (*float64, error) {
ds := goqu.Dialect("postgres").
From(goqu.L(fmt.Sprintf(`%s AS r`, table))).
With("validators", goqu.L("(SELECT dashboard_id, validator_index FROM users_val_dashboards_validators WHERE dashboard_id = ?)", dashboardId)).
Select(
goqu.L("SUM(efficiency_dividend::decimal)").As("efficiency_dividend"),
goqu.L("SUM(efficiency_divisor::decimal)").As("efficiency_divisor"),
).
InnerJoin(goqu.L("validators v"), goqu.On(goqu.L("r.validator_index = v.validator_index"))).
Where(goqu.L("r.validator_index IN (SELECT validator_index FROM validators)"))

type dbResult struct {
EfficiencyDividend decimal.Decimal `db:"efficiency_dividend"`
EfficiencyDivisor decimal.Decimal `db:"efficiency_divisor"`
}
dbRes, err := runQuery[dbResult](ctx, d.clickhouseReader, ds)

*efficiency, err = runQuery[float64](ctx, d.clickhouseReader, ds)
var efficiency *float64
if !dbRes.EfficiencyDivisor.IsZero() {
eff := dbRes.EfficiencyDividend.Div(dbRes.EfficiencyDivisor).InexactFloat64()
efficiency = &eff
}

return err
})
return efficiency, err
}

retrieveRewards(enums.Last24h, &data.Last24hIncome)
retrieveRewards(enums.Last7d, &data.Last7dIncome)
retrieveApr(enums.Last30d, &data.Last30dApr)
retrieveEfficiency("validator_dashboard_data_rolling_30d", &data.Last30dEfficiency)
eg.Go(func() error {
data.Last30dApr, err = retrieveApr(enums.Last30d)
return err
})
eg.Go(func() error {
data.Last30dEfficiency, err = retrieveEfficiency("validator_dashboard_data_rolling_30d")
return err
})

err = eg.Wait()

Expand Down
13 changes: 9 additions & 4 deletions backend/pkg/api/data_access/vdb_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func (d *DataAccessService) getTotalRewardsColumns() string {
type IncomeInfo struct {
Rewards t.ClElValue[decimal.Decimal]

Apr t.ClElValue[float64]
Apr t.ClElValue[*float64]
}

func (d *DataAccessService) getElClAPR(ctx context.Context, dashboardId t.VDBId, groupId int64, timeFrame enums.TimePeriod) (rewardsApr IncomeInfo, err error) {
Expand Down Expand Up @@ -208,12 +208,17 @@ func (d *DataAccessService) getElClAPR(ctx context.Context, dashboardId t.VDBId,
}

// precondition: invested amount and rewards are in the same currency
func calcAPR(rewards, cumulativeDivisor decimal.Decimal, duration time.Duration) float64 {
func calcAPR(rewards, cumulativeDivisor decimal.Decimal, duration time.Duration) *float64 {
if cumulativeDivisor.IsZero() {
return nil
}
var apr float64
if rewards.IsZero() || cumulativeDivisor.IsZero() || duration.Nanoseconds() == 0 {
return 0
return &apr
}
annualizationFactor := decimal.NewFromInt(utils.Year.Nanoseconds()).Div(decimal.NewFromInt(duration.Nanoseconds()))
return rewards.Div(cumulativeDivisor).Mul(annualizationFactor).InexactFloat64()
apr = rewards.Div(cumulativeDivisor).Mul(annualizationFactor).InexactFloat64()
return &apr
}

// converts a cl amount to the main currency
Expand Down
115 changes: 76 additions & 39 deletions backend/pkg/api/data_access/vdb_management.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,49 +377,86 @@ func (d *DataAccessService) GetValidatorDashboardOverview(ctx context.Context, d
return nil
})

retrieveRewardsAndEfficiency := func(timeFrame enums.TimePeriod, rewards *t.ClElValue[decimal.Decimal], apr *t.ClElValue[float64], efficiency *float64) {
// Rewards + APR
eg.Go(func() error {
incomeInfo, err := d.getElClAPR(ctx, dashboardId, -1, timeFrame)
if err != nil {
return err
}
*rewards = incomeInfo.Rewards
*apr = incomeInfo.Apr
return nil
})
retrieveRewards := func(timeFrame enums.TimePeriod) (rewards t.ClElValue[decimal.Decimal], apr t.ClElValue[*float64], err error) {
incomeInfo, err := d.getElClAPR(ctx, dashboardId, -1, timeFrame)
rewards = incomeInfo.Rewards
apr = incomeInfo.Apr
return
}

// Efficiency
eg.Go(func() error {
table, err := timeFrame.Table()
if err != nil {
return err
}
ds := goqu.Dialect("postgres").
From(goqu.L(fmt.Sprintf(`%s AS r`, table))).
With("validators", goqu.L("(SELECT dashboard_id, validator_index FROM users_val_dashboards_validators WHERE dashboard_id = ?)", dashboardId.Id)).
Select(
goqu.L("COALESCE(SUM(efficiency_dividend::Int256) / NULLIF(SUM(efficiency_divisor::Int256), 0), 0)").As("efficiency"),
)

if len(dashboardId.Validators) == 0 {
ds = ds.
InnerJoin(goqu.L("validators v"), goqu.On(goqu.L("r.validator_index = v.validator_index"))).
Where(goqu.L("r.validator_index IN (SELECT validator_index FROM validators)"))
} else {
ds = ds.
Where(goqu.L("r.validator_index IN ?", dashboardId.Validators))
}
retrieveEfficiency := func(timeFrame enums.TimePeriod) (efficiency *float64, err error) {
table, err := timeFrame.Table()
if err != nil {
return
}
ds := goqu.Dialect("postgres").
From(goqu.L(fmt.Sprintf(`%s AS r`, table))).
With("validators", goqu.L("(SELECT dashboard_id, validator_index FROM users_val_dashboards_validators WHERE dashboard_id = ?)", dashboardId.Id)).
Select(
goqu.L("SUM(efficiency_dividend::decimal)").As("efficiency_dividend"),
goqu.L("SUM(efficiency_divisor::decimal)").As("efficiency_divisor"),
)

if len(dashboardId.Validators) == 0 {
ds = ds.
InnerJoin(goqu.L("validators v"), goqu.On(goqu.L("r.validator_index = v.validator_index"))).
Where(goqu.L("r.validator_index IN (SELECT validator_index FROM validators)"))
} else {
ds = ds.
Where(goqu.L("r.validator_index IN ?", dashboardId.Validators))
}

*efficiency, err = runQuery[float64](ctx, d.clickhouseReader, ds)
return err
})
type dbResult struct {
EfficiencyDividend decimal.Decimal `db:"efficiency_dividend"`
EfficiencyDivisor decimal.Decimal `db:"efficiency_divisor"`
}
dbRes, err := runQuery[dbResult](ctx, d.clickhouseReader, ds)
if !dbRes.EfficiencyDivisor.IsZero() {
eff := dbRes.EfficiencyDividend.Div(dbRes.EfficiencyDivisor).InexactFloat64()
efficiency = &eff
}
return
}

retrieveRewardsAndEfficiency(enums.Last24h, &data.Rewards.Last24h, &data.Apr.Last24h, &data.Efficiency.Last24h)
retrieveRewardsAndEfficiency(enums.Last7d, &data.Rewards.Last7d, &data.Apr.Last7d, &data.Efficiency.Last7d)
retrieveRewardsAndEfficiency(enums.Last30d, &data.Rewards.Last30d, &data.Apr.Last30d, &data.Efficiency.Last30d)
retrieveRewardsAndEfficiency(enums.AllTime, &data.Rewards.AllTime, &data.Apr.AllTime, &data.Efficiency.AllTime)
// last 24h
eg.Go(func() error {
data.Rewards.Last24h, data.Apr.Last24h, err = retrieveRewards(enums.Last24h)
return err
})
eg.Go(func() error {
data.Efficiency.Last24h, err = retrieveEfficiency(enums.Last24h)
return err
})

// last 7d
eg.Go(func() error {
data.Rewards.Last7d, data.Apr.Last7d, err = retrieveRewards(enums.Last7d)
return err
})
eg.Go(func() error {
data.Efficiency.Last7d, err = retrieveEfficiency(enums.Last7d)
return err
})

// last 30d
eg.Go(func() error {
data.Rewards.Last30d, data.Apr.Last30d, err = retrieveRewards(enums.Last30d)
return err
})
eg.Go(func() error {
data.Efficiency.Last30d, err = retrieveEfficiency(enums.Last30d)
return err
})

// all time
eg.Go(func() error {
data.Rewards.AllTime, data.Apr.AllTime, err = retrieveRewards(enums.AllTime)
return err
})
eg.Go(func() error {
data.Efficiency.AllTime, err = retrieveEfficiency(enums.AllTime)
return err
})

err = eg.Wait()

Expand Down
33 changes: 13 additions & 20 deletions backend/pkg/api/data_access/vdb_summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,10 @@ func (d *DataAccessService) GetValidatorDashboardSummary(ctx context.Context, da
}
case enums.VDBSummaryColumns.Efficiency:
sortParam = func(resultEntry t.VDBSummaryTableRow) float64 {
return resultEntry.Efficiency
if resultEntry.Efficiency == nil {
return -1
}
return *resultEntry.Efficiency
}
case enums.VDBSummaryColumns.Attestations:
sortParam = func(resultEntry t.VDBSummaryTableRow) float64 {
Expand Down Expand Up @@ -741,23 +744,21 @@ func (d *DataAccessService) GetValidatorDashboardGroupSummary(ctx context.Contex
}

if totalBlockChance > 0 {
ret.Luck.Proposal.Percent = (float64(totalBlocksScheduled)) / totalBlockChance
proposalLuck := (float64(totalBlocksScheduled)) / totalBlockChance
ret.Luck.Proposal.Percent = &proposalLuck

// calculate the average time it takes for the set of validators to propose a single block on average
ret.Luck.Proposal.AverageIntervalSeconds = uint64(time.Duration((luckHours / totalBlockChance) * float64(time.Hour)).Seconds())

ret.Luck.Proposal.ExpectedTimestamp = uint64(lastBlockTs.Unix()) + ret.Luck.Proposal.AverageIntervalSeconds
} else {
ret.Luck.Proposal.Percent = 0
}

if totalSyncExpected == 0 {
ret.Luck.Sync.Percent = 0
} else {
if totalSyncExpected > 0 {
totalSyncSlotDuties := float64(ret.SyncCommittee.StatusCount.Failed) + float64(ret.SyncCommittee.StatusCount.Success)
slotDutiesPerSyncCommittee := float64(utils.SlotsPerSyncCommittee())
syncCommittees := math.Ceil(totalSyncSlotDuties / slotDutiesPerSyncCommittee) // gets the number of sync committees
ret.Luck.Sync.Percent = syncCommittees / totalSyncExpected
syncLuck := syncCommittees / totalSyncExpected
ret.Luck.Sync.Percent = &syncLuck

// calculate the average time it takes for the set of validators to be elected into a sync committee on average
ret.Luck.Sync.AverageIntervalSeconds = uint64(time.Duration((luckHours / totalSyncExpected) * float64(time.Hour)).Seconds())
Expand Down Expand Up @@ -816,24 +817,16 @@ func (d *DataAccessService) GetValidatorDashboardGroupSummary(ctx context.Contex
return ret, nil
}

func calcEfficiencyNulled(dividend, divisor decimal.Decimal) *float64 {
func calcEfficiency(dividend, divisor decimal.Decimal) *float64 {
if divisor.IsZero() {
return nil
}
efficiency := calcEfficiency(dividend, divisor)
return &efficiency
}

func calcEfficiency(dividend, divisor decimal.Decimal) float64 {
if divisor.IsZero() {
return 1
}
eff := dividend.Div(divisor).InexactFloat64()
if eff > 1 {
log.Error(nil, "efficiency is greater than 100%", 1, map[string]interface{}{"efficiency": eff})
eff = 1
}
return eff
return &eff
}

// for summary charts: series id is group id, no stack
Expand Down Expand Up @@ -956,7 +949,7 @@ func (d *DataAccessService) GetValidatorDashboardSummaryChart(ctx context.Contex
}

if !dashboardId.AggregateGroups && requestedGroupsMap[row.GroupId] {
eff := calcEfficiencyNulled(row.EfficiencyDividend, row.EfficiencyDivisor)
eff := calcEfficiency(row.EfficiencyDividend, row.EfficiencyDivisor)
if eff == nil {
continue
}
Expand Down Expand Up @@ -998,7 +991,7 @@ func (d *DataAccessService) GetValidatorDashboardSummaryChart(ctx context.Contex
totalLineGroupId = t.DefaultGroupId
}
for _, row := range totalEfficiencyMap {
data[row.Timestamp][totalLineGroupId] = calcEfficiencyNulled(row.EfficiencyDividend, row.EfficiencyDivisor)
data[row.Timestamp][totalLineGroupId] = calcEfficiency(row.EfficiencyDividend, row.EfficiencyDivisor)
}
groupMap[totalLineGroupId] = true
}
Expand Down
2 changes: 1 addition & 1 deletion backend/pkg/api/handlers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ func (h *HandlerService) GetUserIdByApiKey(r *http.Request) (uint64, error) {
return userId, err
}

// if this is used, user ID should've been stored in context (by GetUserIdStoreMiddleware)
// if this is used, user ID should've been stored in context (by StoreUserIdMiddleware)
func GetUserIdByContext(ctx context.Context) (uint64, error) {
userId, ok := ctx.Value(types.CtxUserIdKey).(uint64)
if !ok {
Expand Down
6 changes: 3 additions & 3 deletions backend/pkg/api/types/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ type Address struct {
}

type LuckItem struct {
Percent float64 `json:"percent"`
ExpectedTimestamp uint64 `json:"expected_timestamp"`
AverageIntervalSeconds uint64 `json:"average_interval_seconds"`
Percent *float64 `json:"percent"`
ExpectedTimestamp uint64 `json:"expected_timestamp"`
AverageIntervalSeconds uint64 `json:"average_interval_seconds"`
}

type Luck struct {
Expand Down
8 changes: 4 additions & 4 deletions backend/pkg/api/types/mobile.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ type MobileWidgetData struct {
ValidatorStateCounts ValidatorStateCounts `json:"validator_state_counts"`
Last24hIncome ClElValue[decimal.Decimal] `json:"last_24h_income" faker:"eth"`
Last7dIncome ClElValue[decimal.Decimal] `json:"last_7d_income" faker:"eth"`
Last30dApr float64 `json:"last_30d_apr"`
Last30dEfficiency float64 `json:"last_30d_efficiency"`
NetworkEfficiency float64 `json:"network_efficiency"`
Last30dApr *float64 `json:"last_30d_apr"`
Last30dEfficiency *float64 `json:"last_30d_efficiency"`
NetworkEfficiency *float64 `json:"network_efficiency"`
RplPrice decimal.Decimal `json:"rpl_price" faker:"eth"`
RplApr float64 `json:"rpl_apr"`
RplApr *float64 `json:"rpl_apr"`
ELCLPrice float64 `json:"el_cl_price"`
}

Expand Down
Loading
Loading