diff --git a/backend/pkg/api/data_access/vdb_helpers.go b/backend/pkg/api/data_access/vdb_helpers.go index 30fd72f61..a969bdd2b 100644 --- a/backend/pkg/api/data_access/vdb_helpers.go +++ b/backend/pkg/api/data_access/vdb_helpers.go @@ -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!) @@ -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 +} diff --git a/backend/pkg/api/data_access/vdb_management.go b/backend/pkg/api/data_access/vdb_management.go index a9e955096..92772772b 100644 --- a/backend/pkg/api/data_access/vdb_management.go +++ b/backend/pkg/api/data_access/vdb_management.go @@ -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 }) diff --git a/backend/pkg/api/data_access/vdb_summary.go b/backend/pkg/api/data_access/vdb_summary.go index 837c6b7bb..1cb931b15 100644 --- a/backend/pkg/api/data_access/vdb_summary.go +++ b/backend/pkg/api/data_access/vdb_summary.go @@ -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) @@ -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() @@ -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 { @@ -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 @@ -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) } @@ -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 }