Skip to content

Commit

Permalink
feat: fill balances field in dashboard group summary
Browse files Browse the repository at this point in the history
See: BEDS-1065
  • Loading branch information
LuccaBitfly committed Jan 9, 2025
1 parent 71cc094 commit efbe235
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 95 deletions.
98 changes: 98 additions & 0 deletions backend/pkg/api/data_access/vdb_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import (
"context"
"database/sql"
"fmt"
"math/big"
"time"

"github.com/doug-martin/goqu/v9"
"github.com/gobitfly/beaconchain/pkg/api/enums"
"github.com/gobitfly/beaconchain/pkg/api/services"
t "github.com/gobitfly/beaconchain/pkg/api/types"
"github.com/gobitfly/beaconchain/pkg/commons/cache"
"github.com/gobitfly/beaconchain/pkg/commons/utils"
"github.com/lib/pq"
"github.com/pkg/errors"
"github.com/shopspring/decimal"
)

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

return timeToWithdrawal
}

type RpOperatorInfo struct {
ValidatorIndex uint64 `db:"validatorindex"`
NodeFee float64 `db:"node_fee"`
NodeDepositBalance decimal.Decimal `db:"node_deposit_balance"`
UserDepositBalance decimal.Decimal `db:"user_deposit_balance"`
}

func (d *DataAccessService) getValidatorDashboardRpOperatorInfo(ctx context.Context, dashboardId t.VDBId) ([]RpOperatorInfo, error) {
var rpOperatorInfo []RpOperatorInfo

ds := goqu.Dialect("postgres").
Select(
goqu.L("v.validatorindex"),
goqu.L("rplm.node_fee"),
goqu.L("rplm.node_deposit_balance"),
goqu.L("rplm.user_deposit_balance")).
From(goqu.L("rocketpool_minipools AS rplm")).
LeftJoin(goqu.L("validators AS v"), goqu.On(goqu.L("rplm.pubkey = v.pubkey"))).
Where(goqu.L("node_deposit_balance IS NOT NULL")).
Where(goqu.L("user_deposit_balance IS NOT NULL"))

if len(dashboardId.Validators) == 0 {
ds = ds.
LeftJoin(goqu.L("users_val_dashboards_validators uvdv"), goqu.On(goqu.L("uvdv.validator_index = v.validatorindex"))).
Where(goqu.L("uvdv.dashboard_id = ?", dashboardId.Id))
} else {
ds = ds.
Where(goqu.L("v.validatorindex = ANY(?)", pq.Array(dashboardId.Validators)))
}

query, args, err := ds.Prepared(true).ToSQL()
if err != nil {
return nil, fmt.Errorf("error preparing query: %w", err)
}

err = d.alloyReader.SelectContext(ctx, &rpOperatorInfo, query, args...)
if err != nil {
return nil, fmt.Errorf("error retrieving rocketpool validators data: %w", err)
}
return rpOperatorInfo, nil
}

func (d *DataAccessService) calculateValidatorDashboardBalance(ctx context.Context, rpOperatorInfo []RpOperatorInfo, validators []t.VDBValidator, validatorMapping *services.ValidatorMapping, protocolModes t.VDBProtocolModes) (t.ValidatorBalances, error) {
balances := t.ValidatorBalances{}

rpValidators := make(map[uint64]RpOperatorInfo)
for _, res := range rpOperatorInfo {
rpValidators[res.ValidatorIndex] = res
}

// Create a new sub-dashboard to get the total cl deposits for non-rocketpool validators
var nonRpDashboardId t.VDBId

for _, validator := range validators {
metadata := validatorMapping.ValidatorMetadata[validator]
validatorBalance := utils.GWeiToWei(big.NewInt(int64(metadata.Balance)))
effectiveBalance := utils.GWeiToWei(big.NewInt(int64(metadata.EffectiveBalance)))

if rpValidator, ok := rpValidators[validator]; ok {
if protocolModes.RocketPool {
// Calculate the balance of the operator
fullDeposit := rpValidator.UserDepositBalance.Add(rpValidator.NodeDepositBalance)
operatorShare := rpValidator.NodeDepositBalance.Div(fullDeposit)
invOperatorShare := decimal.NewFromInt(1).Sub(operatorShare)

base := decimal.Min(decimal.Max(decimal.Zero, validatorBalance.Sub(rpValidator.UserDepositBalance)), rpValidator.NodeDepositBalance)
commission := decimal.Max(decimal.Zero, validatorBalance.Sub(fullDeposit).Mul(invOperatorShare).Mul(decimal.NewFromFloat(rpValidator.NodeFee)))
reward := decimal.Max(decimal.Zero, validatorBalance.Sub(fullDeposit).Mul(operatorShare).Add(commission))

operatorBalance := base.Add(reward)

balances.Total = balances.Total.Add(operatorBalance)
} else {
balances.Total = balances.Total.Add(validatorBalance)
}
balances.StakedEth = balances.StakedEth.Add(rpValidator.NodeDepositBalance)
} else {
balances.Total = balances.Total.Add(validatorBalance)

nonRpDashboardId.Validators = append(nonRpDashboardId.Validators, validator)
}
balances.Effective = balances.Effective.Add(effectiveBalance)
}

// Get the total cl deposits for non-rocketpool validators
if len(nonRpDashboardId.Validators) > 0 {
totalNonRpDeposits, err := d.GetValidatorDashboardTotalClDeposits(ctx, nonRpDashboardId)
if err != nil {
return balances, fmt.Errorf("error retrieving total cl deposits for non-rocketpool validators: %w", err)
}
balances.StakedEth = balances.StakedEth.Add(totalNonRpDeposits.TotalAmount)
}
return balances, nil
}
86 changes: 5 additions & 81 deletions backend/pkg/api/data_access/vdb_management.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,92 +364,16 @@ func (d *DataAccessService) GetValidatorDashboardOverview(ctx context.Context, d
}
}

// Find rocketpool validators
type RpOperatorInfo struct {
ValidatorIndex uint64 `db:"validatorindex"`
NodeFee float64 `db:"node_fee"`
NodeDepositBalance decimal.Decimal `db:"node_deposit_balance"`
UserDepositBalance decimal.Decimal `db:"user_deposit_balance"`
}
var queryResult []RpOperatorInfo

ds := goqu.Dialect("postgres").
Select(
goqu.L("v.validatorindex"),
goqu.L("rplm.node_fee"),
goqu.L("rplm.node_deposit_balance"),
goqu.L("rplm.user_deposit_balance")).
From(goqu.L("rocketpool_minipools AS rplm")).
LeftJoin(goqu.L("validators AS v"), goqu.On(goqu.L("rplm.pubkey = v.pubkey"))).
Where(goqu.L("node_deposit_balance IS NOT NULL")).
Where(goqu.L("user_deposit_balance IS NOT NULL"))

if len(dashboardId.Validators) == 0 {
ds = ds.
LeftJoin(goqu.L("users_val_dashboards_validators uvdv"), goqu.On(goqu.L("uvdv.validator_index = v.validatorindex"))).
Where(goqu.L("uvdv.dashboard_id = ?", dashboardId.Id))
} else {
ds = ds.
Where(goqu.L("v.validatorindex = ANY(?)", pq.Array(dashboardId.Validators)))
}

query, args, err := ds.Prepared(true).ToSQL()
rpOperatorInfo, err := d.getValidatorDashboardRpOperatorInfo(ctx, dashboardId)
if err != nil {
return fmt.Errorf("error preparing query: %w", err)
return err
}

err = d.alloyReader.SelectContext(ctx, &queryResult, query, args...)
balances, err := d.calculateValidatorDashboardBalance(ctx, rpOperatorInfo, validators, validatorMapping, protocolModes)
if err != nil {
return fmt.Errorf("error retrieving rocketpool validators data: %w", err)
}

rpValidators := make(map[uint64]RpOperatorInfo)
for _, res := range queryResult {
rpValidators[res.ValidatorIndex] = res
}

// Create a new sub-dashboard to get the total cl deposits for non-rocketpool validators
var nonRpDashboardId t.VDBId

for _, validator := range validators {
metadata := validatorMapping.ValidatorMetadata[validator]
validatorBalance := utils.GWeiToWei(big.NewInt(int64(metadata.Balance)))
effectiveBalance := utils.GWeiToWei(big.NewInt(int64(metadata.EffectiveBalance)))

if rpValidator, ok := rpValidators[validator]; ok {
if protocolModes.RocketPool {
// Calculate the balance of the operator
fullDeposit := rpValidator.UserDepositBalance.Add(rpValidator.NodeDepositBalance)
operatorShare := rpValidator.NodeDepositBalance.Div(fullDeposit)
invOperatorShare := decimal.NewFromInt(1).Sub(operatorShare)

base := decimal.Min(decimal.Max(decimal.Zero, validatorBalance.Sub(rpValidator.UserDepositBalance)), rpValidator.NodeDepositBalance)
commission := decimal.Max(decimal.Zero, validatorBalance.Sub(fullDeposit).Mul(invOperatorShare).Mul(decimal.NewFromFloat(rpValidator.NodeFee)))
reward := decimal.Max(decimal.Zero, validatorBalance.Sub(fullDeposit).Mul(operatorShare).Add(commission))

operatorBalance := base.Add(reward)

data.Balances.Total = data.Balances.Total.Add(operatorBalance)
} else {
data.Balances.Total = data.Balances.Total.Add(validatorBalance)
}
data.Balances.StakedEth = data.Balances.StakedEth.Add(rpValidator.NodeDepositBalance)
} else {
data.Balances.Total = data.Balances.Total.Add(validatorBalance)

nonRpDashboardId.Validators = append(nonRpDashboardId.Validators, validator)
}
data.Balances.Effective = data.Balances.Effective.Add(effectiveBalance)
}

// Get the total cl deposits for non-rocketpool validators
if len(nonRpDashboardId.Validators) > 0 {
totalNonRpDeposits, err := d.GetValidatorDashboardTotalClDeposits(ctx, nonRpDashboardId)
if err != nil {
return fmt.Errorf("error retrieving total cl deposits for non-rocketpool validators: %w", err)
}
data.Balances.StakedEth = data.Balances.StakedEth.Add(totalNonRpDeposits.TotalAmount)
return err
}
data.Balances = balances

return nil
})
Expand Down
36 changes: 22 additions & 14 deletions backend/pkg/api/data_access/vdb_summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,11 +532,6 @@ func (d *DataAccessService) GetValidatorDashboardGroupSummary(ctx context.Contex
return nil, err
}

validators := make([]t.VDBValidator, 0)
if dashboardId.Validators != nil {
validators = dashboardId.Validators
}

getLastScheduledBlockAndSyncDate := func() (time.Time, time.Time, error) {
// we need to go to the all time table for last scheduled block/sync committee epoch
clickhouseTotalTable, _, err := d.getTablesForPeriod(enums.AllTime)
Expand All @@ -557,7 +552,7 @@ func (d *DataAccessService) GetValidatorDashboardGroupSummary(ctx context.Contex
Where(goqu.L("validator_index IN (SELECT validator_index FROM validators)"))
} else {
ds = ds.
Where(goqu.L("validator_index IN ?", validators))
Where(goqu.L("validator_index IN ?", dashboardId.Validators))
}

query, args, err := ds.Prepared(true).ToSQL()
Expand Down Expand Up @@ -610,7 +605,7 @@ func (d *DataAccessService) GetValidatorDashboardGroupSummary(ctx context.Contex
Where(goqu.L("validator_index IN (SELECT validator_index FROM validators)"))
} else {
ds = ds.
Where(goqu.L("validator_index IN ?", validators))
Where(goqu.L("validator_index IN ?", dashboardId.Validators))
}

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

validatorArr := make([]t.VDBValidator, 0)
validators := make([]t.VDBValidator, 0)
for _, row := range rows {
validatorArr = append(validatorArr, t.VDBValidator(row.ValidatorIndex))
validators = append(validators, t.VDBValidator(row.ValidatorIndex))
totalAttestationRewards += row.AttestationReward
totalIdealAttestationRewards += row.AttestationIdealReward

Expand Down Expand Up @@ -756,13 +751,9 @@ func (d *DataAccessService) GetValidatorDashboardGroupSummary(ctx context.Contex
return nil, err
}

if len(validators) > 0 {
validatorArr = validators
}

pastSyncPeriodCutoff := utils.SyncPeriodOfEpoch(rows[0].EpochStart)
currentSyncPeriod := utils.SyncPeriodOfEpoch(latestEpoch)
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)
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)
if err != nil {
return nil, fmt.Errorf("error retrieving past sync committee count: %w", err)
}
Expand Down Expand Up @@ -848,6 +839,23 @@ func (d *DataAccessService) GetValidatorDashboardGroupSummary(ctx context.Contex
}
ret.Efficiency = utils.CalculateTotalEfficiency(attestationEfficiency, proposerEfficiency, syncEfficiency)

now := time.Now()
rpOperatorInfo, err := d.getValidatorDashboardRpOperatorInfo(ctx, dashboardId)
if err != nil {
return nil, err
}
validatorMapping, err := d.services.GetCurrentValidatorMapping()
if err != nil {
return nil, err
}
balances, err := d.calculateValidatorDashboardBalance(ctx, rpOperatorInfo, validators, validatorMapping, protocolModes)
if err != nil {
return nil, err
}
ret.Balances = balances
since := time.Since(now)
log.Info(since)

return ret, nil
}

Expand Down

0 comments on commit efbe235

Please sign in to comment.