Skip to content

Commit efbe235

Browse files
committed
feat: fill balances field in dashboard group summary
See: BEDS-1065
1 parent 71cc094 commit efbe235

File tree

3 files changed

+125
-95
lines changed

3 files changed

+125
-95
lines changed

backend/pkg/api/data_access/vdb_helpers.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@ import (
44
"context"
55
"database/sql"
66
"fmt"
7+
"math/big"
78
"time"
89

910
"github.com/doug-martin/goqu/v9"
1011
"github.com/gobitfly/beaconchain/pkg/api/enums"
12+
"github.com/gobitfly/beaconchain/pkg/api/services"
1113
t "github.com/gobitfly/beaconchain/pkg/api/types"
1214
"github.com/gobitfly/beaconchain/pkg/commons/cache"
1315
"github.com/gobitfly/beaconchain/pkg/commons/utils"
1416
"github.com/lib/pq"
1517
"github.com/pkg/errors"
18+
"github.com/shopspring/decimal"
1619
)
1720

1821
//////////////////// Helper functions (must be used by more than one VDB endpoint!)
@@ -130,3 +133,98 @@ func (d *DataAccessService) getTimeToNextWithdrawal(distance uint64) time.Time {
130133

131134
return timeToWithdrawal
132135
}
136+
137+
type RpOperatorInfo struct {
138+
ValidatorIndex uint64 `db:"validatorindex"`
139+
NodeFee float64 `db:"node_fee"`
140+
NodeDepositBalance decimal.Decimal `db:"node_deposit_balance"`
141+
UserDepositBalance decimal.Decimal `db:"user_deposit_balance"`
142+
}
143+
144+
func (d *DataAccessService) getValidatorDashboardRpOperatorInfo(ctx context.Context, dashboardId t.VDBId) ([]RpOperatorInfo, error) {
145+
var rpOperatorInfo []RpOperatorInfo
146+
147+
ds := goqu.Dialect("postgres").
148+
Select(
149+
goqu.L("v.validatorindex"),
150+
goqu.L("rplm.node_fee"),
151+
goqu.L("rplm.node_deposit_balance"),
152+
goqu.L("rplm.user_deposit_balance")).
153+
From(goqu.L("rocketpool_minipools AS rplm")).
154+
LeftJoin(goqu.L("validators AS v"), goqu.On(goqu.L("rplm.pubkey = v.pubkey"))).
155+
Where(goqu.L("node_deposit_balance IS NOT NULL")).
156+
Where(goqu.L("user_deposit_balance IS NOT NULL"))
157+
158+
if len(dashboardId.Validators) == 0 {
159+
ds = ds.
160+
LeftJoin(goqu.L("users_val_dashboards_validators uvdv"), goqu.On(goqu.L("uvdv.validator_index = v.validatorindex"))).
161+
Where(goqu.L("uvdv.dashboard_id = ?", dashboardId.Id))
162+
} else {
163+
ds = ds.
164+
Where(goqu.L("v.validatorindex = ANY(?)", pq.Array(dashboardId.Validators)))
165+
}
166+
167+
query, args, err := ds.Prepared(true).ToSQL()
168+
if err != nil {
169+
return nil, fmt.Errorf("error preparing query: %w", err)
170+
}
171+
172+
err = d.alloyReader.SelectContext(ctx, &rpOperatorInfo, query, args...)
173+
if err != nil {
174+
return nil, fmt.Errorf("error retrieving rocketpool validators data: %w", err)
175+
}
176+
return rpOperatorInfo, nil
177+
}
178+
179+
func (d *DataAccessService) calculateValidatorDashboardBalance(ctx context.Context, rpOperatorInfo []RpOperatorInfo, validators []t.VDBValidator, validatorMapping *services.ValidatorMapping, protocolModes t.VDBProtocolModes) (t.ValidatorBalances, error) {
180+
balances := t.ValidatorBalances{}
181+
182+
rpValidators := make(map[uint64]RpOperatorInfo)
183+
for _, res := range rpOperatorInfo {
184+
rpValidators[res.ValidatorIndex] = res
185+
}
186+
187+
// Create a new sub-dashboard to get the total cl deposits for non-rocketpool validators
188+
var nonRpDashboardId t.VDBId
189+
190+
for _, validator := range validators {
191+
metadata := validatorMapping.ValidatorMetadata[validator]
192+
validatorBalance := utils.GWeiToWei(big.NewInt(int64(metadata.Balance)))
193+
effectiveBalance := utils.GWeiToWei(big.NewInt(int64(metadata.EffectiveBalance)))
194+
195+
if rpValidator, ok := rpValidators[validator]; ok {
196+
if protocolModes.RocketPool {
197+
// Calculate the balance of the operator
198+
fullDeposit := rpValidator.UserDepositBalance.Add(rpValidator.NodeDepositBalance)
199+
operatorShare := rpValidator.NodeDepositBalance.Div(fullDeposit)
200+
invOperatorShare := decimal.NewFromInt(1).Sub(operatorShare)
201+
202+
base := decimal.Min(decimal.Max(decimal.Zero, validatorBalance.Sub(rpValidator.UserDepositBalance)), rpValidator.NodeDepositBalance)
203+
commission := decimal.Max(decimal.Zero, validatorBalance.Sub(fullDeposit).Mul(invOperatorShare).Mul(decimal.NewFromFloat(rpValidator.NodeFee)))
204+
reward := decimal.Max(decimal.Zero, validatorBalance.Sub(fullDeposit).Mul(operatorShare).Add(commission))
205+
206+
operatorBalance := base.Add(reward)
207+
208+
balances.Total = balances.Total.Add(operatorBalance)
209+
} else {
210+
balances.Total = balances.Total.Add(validatorBalance)
211+
}
212+
balances.StakedEth = balances.StakedEth.Add(rpValidator.NodeDepositBalance)
213+
} else {
214+
balances.Total = balances.Total.Add(validatorBalance)
215+
216+
nonRpDashboardId.Validators = append(nonRpDashboardId.Validators, validator)
217+
}
218+
balances.Effective = balances.Effective.Add(effectiveBalance)
219+
}
220+
221+
// Get the total cl deposits for non-rocketpool validators
222+
if len(nonRpDashboardId.Validators) > 0 {
223+
totalNonRpDeposits, err := d.GetValidatorDashboardTotalClDeposits(ctx, nonRpDashboardId)
224+
if err != nil {
225+
return balances, fmt.Errorf("error retrieving total cl deposits for non-rocketpool validators: %w", err)
226+
}
227+
balances.StakedEth = balances.StakedEth.Add(totalNonRpDeposits.TotalAmount)
228+
}
229+
return balances, nil
230+
}

backend/pkg/api/data_access/vdb_management.go

Lines changed: 5 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -364,92 +364,16 @@ func (d *DataAccessService) GetValidatorDashboardOverview(ctx context.Context, d
364364
}
365365
}
366366

367-
// Find rocketpool validators
368-
type RpOperatorInfo struct {
369-
ValidatorIndex uint64 `db:"validatorindex"`
370-
NodeFee float64 `db:"node_fee"`
371-
NodeDepositBalance decimal.Decimal `db:"node_deposit_balance"`
372-
UserDepositBalance decimal.Decimal `db:"user_deposit_balance"`
373-
}
374-
var queryResult []RpOperatorInfo
375-
376-
ds := goqu.Dialect("postgres").
377-
Select(
378-
goqu.L("v.validatorindex"),
379-
goqu.L("rplm.node_fee"),
380-
goqu.L("rplm.node_deposit_balance"),
381-
goqu.L("rplm.user_deposit_balance")).
382-
From(goqu.L("rocketpool_minipools AS rplm")).
383-
LeftJoin(goqu.L("validators AS v"), goqu.On(goqu.L("rplm.pubkey = v.pubkey"))).
384-
Where(goqu.L("node_deposit_balance IS NOT NULL")).
385-
Where(goqu.L("user_deposit_balance IS NOT NULL"))
386-
387-
if len(dashboardId.Validators) == 0 {
388-
ds = ds.
389-
LeftJoin(goqu.L("users_val_dashboards_validators uvdv"), goqu.On(goqu.L("uvdv.validator_index = v.validatorindex"))).
390-
Where(goqu.L("uvdv.dashboard_id = ?", dashboardId.Id))
391-
} else {
392-
ds = ds.
393-
Where(goqu.L("v.validatorindex = ANY(?)", pq.Array(dashboardId.Validators)))
394-
}
395-
396-
query, args, err := ds.Prepared(true).ToSQL()
367+
rpOperatorInfo, err := d.getValidatorDashboardRpOperatorInfo(ctx, dashboardId)
397368
if err != nil {
398-
return fmt.Errorf("error preparing query: %w", err)
369+
return err
399370
}
400371

401-
err = d.alloyReader.SelectContext(ctx, &queryResult, query, args...)
372+
balances, err := d.calculateValidatorDashboardBalance(ctx, rpOperatorInfo, validators, validatorMapping, protocolModes)
402373
if err != nil {
403-
return fmt.Errorf("error retrieving rocketpool validators data: %w", err)
404-
}
405-
406-
rpValidators := make(map[uint64]RpOperatorInfo)
407-
for _, res := range queryResult {
408-
rpValidators[res.ValidatorIndex] = res
409-
}
410-
411-
// Create a new sub-dashboard to get the total cl deposits for non-rocketpool validators
412-
var nonRpDashboardId t.VDBId
413-
414-
for _, validator := range validators {
415-
metadata := validatorMapping.ValidatorMetadata[validator]
416-
validatorBalance := utils.GWeiToWei(big.NewInt(int64(metadata.Balance)))
417-
effectiveBalance := utils.GWeiToWei(big.NewInt(int64(metadata.EffectiveBalance)))
418-
419-
if rpValidator, ok := rpValidators[validator]; ok {
420-
if protocolModes.RocketPool {
421-
// Calculate the balance of the operator
422-
fullDeposit := rpValidator.UserDepositBalance.Add(rpValidator.NodeDepositBalance)
423-
operatorShare := rpValidator.NodeDepositBalance.Div(fullDeposit)
424-
invOperatorShare := decimal.NewFromInt(1).Sub(operatorShare)
425-
426-
base := decimal.Min(decimal.Max(decimal.Zero, validatorBalance.Sub(rpValidator.UserDepositBalance)), rpValidator.NodeDepositBalance)
427-
commission := decimal.Max(decimal.Zero, validatorBalance.Sub(fullDeposit).Mul(invOperatorShare).Mul(decimal.NewFromFloat(rpValidator.NodeFee)))
428-
reward := decimal.Max(decimal.Zero, validatorBalance.Sub(fullDeposit).Mul(operatorShare).Add(commission))
429-
430-
operatorBalance := base.Add(reward)
431-
432-
data.Balances.Total = data.Balances.Total.Add(operatorBalance)
433-
} else {
434-
data.Balances.Total = data.Balances.Total.Add(validatorBalance)
435-
}
436-
data.Balances.StakedEth = data.Balances.StakedEth.Add(rpValidator.NodeDepositBalance)
437-
} else {
438-
data.Balances.Total = data.Balances.Total.Add(validatorBalance)
439-
440-
nonRpDashboardId.Validators = append(nonRpDashboardId.Validators, validator)
441-
}
442-
data.Balances.Effective = data.Balances.Effective.Add(effectiveBalance)
443-
}
444-
445-
// Get the total cl deposits for non-rocketpool validators
446-
if len(nonRpDashboardId.Validators) > 0 {
447-
totalNonRpDeposits, err := d.GetValidatorDashboardTotalClDeposits(ctx, nonRpDashboardId)
448-
if err != nil {
449-
return fmt.Errorf("error retrieving total cl deposits for non-rocketpool validators: %w", err)
450-
}
451-
data.Balances.StakedEth = data.Balances.StakedEth.Add(totalNonRpDeposits.TotalAmount)
374+
return err
452375
}
376+
data.Balances = balances
453377

454378
return nil
455379
})

backend/pkg/api/data_access/vdb_summary.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -532,11 +532,6 @@ func (d *DataAccessService) GetValidatorDashboardGroupSummary(ctx context.Contex
532532
return nil, err
533533
}
534534

535-
validators := make([]t.VDBValidator, 0)
536-
if dashboardId.Validators != nil {
537-
validators = dashboardId.Validators
538-
}
539-
540535
getLastScheduledBlockAndSyncDate := func() (time.Time, time.Time, error) {
541536
// we need to go to the all time table for last scheduled block/sync committee epoch
542537
clickhouseTotalTable, _, err := d.getTablesForPeriod(enums.AllTime)
@@ -557,7 +552,7 @@ func (d *DataAccessService) GetValidatorDashboardGroupSummary(ctx context.Contex
557552
Where(goqu.L("validator_index IN (SELECT validator_index FROM validators)"))
558553
} else {
559554
ds = ds.
560-
Where(goqu.L("validator_index IN ?", validators))
555+
Where(goqu.L("validator_index IN ?", dashboardId.Validators))
561556
}
562557

563558
query, args, err := ds.Prepared(true).ToSQL()
@@ -610,7 +605,7 @@ func (d *DataAccessService) GetValidatorDashboardGroupSummary(ctx context.Contex
610605
Where(goqu.L("validator_index IN (SELECT validator_index FROM validators)"))
611606
} else {
612607
ds = ds.
613-
Where(goqu.L("validator_index IN ?", validators))
608+
Where(goqu.L("validator_index IN ?", dashboardId.Validators))
614609
}
615610

616611
type QueryResult struct {
@@ -686,9 +681,9 @@ func (d *DataAccessService) GetValidatorDashboardGroupSummary(ctx context.Contex
686681
totalBlocksScheduled := uint32(0)
687682
totalBlocksProposed := uint32(0)
688683

689-
validatorArr := make([]t.VDBValidator, 0)
684+
validators := make([]t.VDBValidator, 0)
690685
for _, row := range rows {
691-
validatorArr = append(validatorArr, t.VDBValidator(row.ValidatorIndex))
686+
validators = append(validators, t.VDBValidator(row.ValidatorIndex))
692687
totalAttestationRewards += row.AttestationReward
693688
totalIdealAttestationRewards += row.AttestationIdealReward
694689

@@ -756,13 +751,9 @@ func (d *DataAccessService) GetValidatorDashboardGroupSummary(ctx context.Contex
756751
return nil, err
757752
}
758753

759-
if len(validators) > 0 {
760-
validatorArr = validators
761-
}
762-
763754
pastSyncPeriodCutoff := utils.SyncPeriodOfEpoch(rows[0].EpochStart)
764755
currentSyncPeriod := utils.SyncPeriodOfEpoch(latestEpoch)
765-
err = d.readerDb.GetContext(ctx, &ret.SyncCommitteeCount.PastPeriods, `SELECT COUNT(*) FROM sync_committees WHERE period >= $1 AND period < $2 AND validatorindex = ANY($3)`, pastSyncPeriodCutoff, currentSyncPeriod, validatorArr)
756+
err = d.readerDb.GetContext(ctx, &ret.SyncCommitteeCount.PastPeriods, `SELECT COUNT(*) FROM sync_committees WHERE period >= $1 AND period < $2 AND validatorindex = ANY($3)`, pastSyncPeriodCutoff, currentSyncPeriod, validators)
766757
if err != nil {
767758
return nil, fmt.Errorf("error retrieving past sync committee count: %w", err)
768759
}
@@ -848,6 +839,23 @@ func (d *DataAccessService) GetValidatorDashboardGroupSummary(ctx context.Contex
848839
}
849840
ret.Efficiency = utils.CalculateTotalEfficiency(attestationEfficiency, proposerEfficiency, syncEfficiency)
850841

842+
now := time.Now()
843+
rpOperatorInfo, err := d.getValidatorDashboardRpOperatorInfo(ctx, dashboardId)
844+
if err != nil {
845+
return nil, err
846+
}
847+
validatorMapping, err := d.services.GetCurrentValidatorMapping()
848+
if err != nil {
849+
return nil, err
850+
}
851+
balances, err := d.calculateValidatorDashboardBalance(ctx, rpOperatorInfo, validators, validatorMapping, protocolModes)
852+
if err != nil {
853+
return nil, err
854+
}
855+
ret.Balances = balances
856+
since := time.Since(now)
857+
log.Info(since)
858+
851859
return ret, nil
852860
}
853861

0 commit comments

Comments
 (0)