Skip to content

Commit

Permalink
(BIDS-2368) optimize withdrawal queries (#2549)
Browse files Browse the repository at this point in the history
  • Loading branch information
LuccaBitfly authored Oct 2, 2023
1 parent 7121f4f commit f0a817e
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 93 deletions.
95 changes: 33 additions & 62 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -2602,28 +2602,6 @@ func GetAddressWithdrawalsTotal(address []byte) (uint64, error) {
return total, nil
}

func GetDashboardWithdrawalsCount(validators []uint64) (uint64, error) {
var count uint64
validatorFilter := pq.Array(validators)
err := ReaderDb.Get(&count, `
/*+
BitmapScan(w)
NestLoop(b w)
*/
SELECT count(*)
FROM blocks_withdrawals w
INNER JOIN blocks b ON b.blockroot = w.block_root AND b.status = '1'
WHERE w.validatorindex = Any($1)`, validatorFilter)
if err != nil {
if err == sql.ErrNoRows {
return 0, nil
}
return 0, fmt.Errorf("error getting dashboard validator blocks_withdrawals count for validators: %d: %w", validators, err)
}

return count, nil
}

func GetDashboardWithdrawals(validators []uint64, limit uint64, offset uint64, orderBy string, orderDir string) ([]*types.Withdrawals, error) {
var withdrawals []*types.Withdrawals
if limit == 0 {
Expand Down Expand Up @@ -2656,57 +2634,61 @@ func GetDashboardWithdrawals(validators []uint64, limit uint64, offset uint64, o
return withdrawals, nil
}

func GetValidatorWithdrawalsCount(validator uint64) (count, lastWithdrawalEpoch uint64, err error) {

type dbResponse struct {
Count uint64 `db:"withdrawals_count"`
LastWithdrawalSlot uint64 `db:"last_withdawal_slot"`
func GetTotalWithdrawalsCount(validators []uint64) (uint64, error) {
var count uint64
validatorFilter := pq.Array(validators)
lastExportedDay, err := GetLastExportedStatisticDay()
if err != nil {
return 0, fmt.Errorf("error getting latest exported statistic day for withdrawals count: %w", err)
}
_, lastEpochOfDay := utils.GetFirstAndLastEpochForDay(lastExportedDay)
cutoffSlot := (lastEpochOfDay * utils.Config.Chain.Config.SlotsPerEpoch) + 1

r := &dbResponse{}
err = ReaderDb.Get(r, `
/*+
BitmapScan(w)
NestLoop(b w)
*/
SELECT count(*) as withdrawals_count, COALESCE(max(block_slot), 0) as last_withdawal_slot
FROM blocks_withdrawals w
INNER JOIN blocks b ON b.blockroot = w.block_root AND b.status = '1'
WHERE w.validatorindex = $1`, validator)
err = ReaderDb.Get(&count, `
WITH today AS (
SELECT COUNT(*) as count_today
FROM blocks_withdrawals w
INNER JOIN blocks b ON b.blockroot = w.block_root AND b.status = '1'
WHERE w.validatorindex = ANY($1) AND w.block_slot >= $2
),
stats AS (
SELECT COALESCE(SUM(withdrawals), 0) as total_count
FROM validator_stats
WHERE validatorindex = ANY($1)
)
SELECT today.count_today + stats.total_count
FROM today, stats;`, validatorFilter, cutoffSlot)
if err != nil {
if err == sql.ErrNoRows {
return 0, 0, nil
return 0, nil
}
return 0, 0, fmt.Errorf("error getting validator blocks_withdrawals count for validator: %d: %w", validator, err)
return 0, fmt.Errorf("error getting dashboard validator blocks_withdrawals count for validators: %d: %w", validators, err)
}

return r.Count, r.LastWithdrawalSlot / utils.Config.Chain.Config.SlotsPerEpoch, nil
return count, nil
}

func GetLastWithdrawalEpoch(validators []uint64) (map[uint64]uint64, error) {

type dbResponse struct {
var dbResponse []struct {
ValidatorIndex uint64 `db:"validatorindex"`
LastWithdrawalSlot uint64 `db:"last_withdawal_slot"`
}

res := make(map[uint64]uint64)

r := make([]*dbResponse, 0)
err := ReaderDb.Get(r, `
SELECT w.validatorindex, COALESCE(max(block_slot), 0) as last_withdawal_slot
FROM blocks_withdrawals w
INNER JOIN blocks b ON b.blockroot = w.block_root AND b.status = '1'
WHERE w.validatorindex = ANY($1)
GROUP BY w.validatorindex`, validators)
err := ReaderDb.Select(&dbResponse, `
SELECT w.validatorindex as validatorindex, COALESCE(max(block_slot), 0) as last_withdawal_slot
FROM blocks_withdrawals w
INNER JOIN blocks b ON b.blockroot = w.block_root AND b.status = '1'
WHERE w.validatorindex = ANY($1)
GROUP BY w.validatorindex`, validators)
if err != nil {
if err == sql.ErrNoRows {
return res, nil
}
return nil, fmt.Errorf("error getting validator blocks_withdrawals count for validators: %d: %w", validators, err)
}

for _, row := range r {
for _, row := range dbResponse {
res[row.ValidatorIndex] = row.LastWithdrawalSlot / utils.Config.Chain.Config.SlotsPerEpoch
}

Expand Down Expand Up @@ -3252,17 +3234,6 @@ func GetFirstActivationEpoch(validators []uint64, firstActivationEpoch *uint64)
`, validatorsPQArray)
}

func GetTotalValidatorWithdrawals(validators []uint64, totalWithdrawals *uint64) error {
validatorsPQArray := pq.Array(validators)
return ReaderDb.Get(totalWithdrawals, `
SELECT
COALESCE(sum(w.amount), 0)
FROM blocks_withdrawals w
INNER JOIN blocks b ON b.blockroot = w.block_root AND b.status = '1'
WHERE validatorindex = ANY($1)
`, validatorsPQArray)
}

func GetValidatorDepositsForSlots(validators []uint64, fromSlot uint64, toSlot uint64, deposits *uint64) error {
validatorsPQArray := pq.Array(validators)
return ReaderDb.Get(deposits, `
Expand Down
60 changes: 39 additions & 21 deletions handlers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1361,18 +1361,10 @@ func getGeneralValidatorInfoForAppDashboard(queryIndices []uint64) ([]interface{
COALESCE(validator_performance.cl_performance_365d, 0) AS performance365d,
COALESCE(validator_performance.cl_performance_total, 0) AS performanceTotal,
COALESCE(validator_performance.rank7d, 0) AS rank7d,
((validator_performance.rank7d::float * 100) / COALESCE((SELECT total_count FROM maxValidatorIndex), 1)) as rankpercentage,
w.total as total_withdrawals
((validator_performance.rank7d::float * 100) / COALESCE((SELECT total_count FROM maxValidatorIndex), 1)) as rankpercentage
FROM validators
LEFT JOIN validator_performance ON validators.validatorindex = validator_performance.validatorindex
LEFT JOIN validator_names ON validator_names.publickey = validators.pubkey
LEFT JOIN (
SELECT validatorindex as index, COALESCE(sum(amount), 0) as total
FROM blocks_withdrawals w
INNER JOIN blocks b ON b.blockroot = w.block_root AND status = '1'
WHERE validatorindex = ANY($1)
GROUP BY validatorindex
) as w ON w.index = validators.validatorindex
WHERE validators.validatorindex = ANY($1)
ORDER BY validators.validatorindex`, pq.Array(queryIndices))
if err != nil {
Expand Down Expand Up @@ -1556,31 +1548,57 @@ func apiValidator(w http.ResponseWriter, r *http.Request) {
return
}

lastExportedDay, err := services.LatestExportedStatisticDay()
if err != nil {
sendServerErrorResponse(w, r.URL.String(), "error retrieving data, please try again later")
return
}
_, lastEpochOfDay := utils.GetFirstAndLastEpochForDay(lastExportedDay)
cutoffSlot := (lastEpochOfDay * utils.Config.Chain.Config.SlotsPerEpoch) + 1

data := make([]*ApiValidatorResponse, 0)

err = db.ReaderDb.Select(&data, `
WITH today AS (
SELECT
w.validatorindex,
COALESCE(SUM(w.amount), 0) as amount_today
FROM blocks_withdrawals w
INNER JOIN blocks b ON b.blockroot = w.block_root AND b.status = '1'
WHERE w.validatorindex = ANY($1) AND w.block_slot >= $2
GROUP BY w.validatorindex
),
stats AS (
SELECT
vs.validatorindex,
COALESCE(SUM(vs.withdrawals_amount), 0) as total_amount
FROM validator_stats vs
WHERE vs.validatorindex = ANY($1)
GROUP BY vs.validatorindex
),
withdrawals_summary AS (
SELECT
COALESCE(t.validatorindex, s.validatorindex) as validatorindex,
COALESCE(t.amount_today, 0) + COALESCE(s.total_amount, 0) as total
FROM today t
FULL JOIN stats s ON t.validatorindex = s.validatorindex
)
SELECT
validatorindex, '0x' || encode(pubkey, 'hex') as pubkey, withdrawableepoch,
v.validatorindex, '0x' || encode(pubkey, 'hex') as pubkey, withdrawableepoch,
'0x' || encode(withdrawalcredentials, 'hex') as withdrawalcredentials,
slashed,
activationeligibilityepoch,
activationepoch,
exitepoch,
status,
COALESCE(n.name, '') AS name,
COALESCE(w.total, 0) as total_withdrawals
COALESCE(ws.total, 0) as total_withdrawals
FROM validators v
LEFT JOIN validator_names n ON n.publickey = v.pubkey
LEFT JOIN (
SELECT validatorindex as index, COALESCE(sum(amount), 0) as total
FROM blocks_withdrawals w
INNER JOIN blocks b ON b.blockroot = w.block_root AND status = '1'
WHERE validatorindex = ANY($1)
GROUP BY validatorindex
) as w ON w.index = v.validatorindex
WHERE validatorindex = ANY($1)
ORDER BY validatorindex;
`, pq.Array(queryIndices))
LEFT JOIN withdrawals_summary ws ON ws.validatorindex = v.validatorindex
WHERE v.validatorindex = ANY($1)
ORDER BY v.validatorindex;
`, pq.Array(queryIndices), cutoffSlot)
if err != nil {
logger.Warnf("error retrieving validator data from db: %v", err)
sendErrorResponse(w, r.URL.String(), "could not retrieve db results")
Expand Down
5 changes: 0 additions & 5 deletions handlers/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,6 @@ func GetValidatorEarnings(validators []uint64, currency string) (*types.Validato
return db.GetFirstActivationEpoch(validators, &firstActivationEpoch)
})

var totalWithdrawals uint64
g.Go(func() error {
return db.GetTotalValidatorWithdrawals(validators, &totalWithdrawals)
})

var lastDeposits uint64
var lastWithdrawals uint64
var lastBalance uint64
Expand Down
5 changes: 3 additions & 2 deletions handlers/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,10 +387,11 @@ func getNextWithdrawalRow(queryValidators []uint64) ([][]interface{}, error) {
return nil, nil
}

_, lastWithdrawnEpoch, err := db.GetValidatorWithdrawalsCount(nextValidator.Index)
lastWithdrawnEpochs, err := db.GetLastWithdrawalEpoch([]uint64{nextValidator.Index})
if err != nil {
return nil, err
}
lastWithdrawnEpoch := lastWithdrawnEpochs[nextValidator.Index]

distance, err := GetWithdrawableCountFromCursor(epoch, nextValidator.Index, *stats.LatestValidatorWithdrawalIndex)
if err != nil {
Expand Down Expand Up @@ -628,7 +629,7 @@ func DashboardDataWithdrawals(w http.ResponseWriter, r *http.Request) {

length := uint64(10)

withdrawalCount, err := db.GetDashboardWithdrawalsCount(validatorIndices)
withdrawalCount, err := db.GetTotalWithdrawalsCount(validatorIndices)
if err != nil {
utils.LogError(err, fmt.Errorf("error retrieving dashboard validator withdrawals count: %v", err), 0)
http.Error(w, "Internal server error", http.StatusInternalServerError)
Expand Down
11 changes: 8 additions & 3 deletions handlers/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,12 +436,17 @@ func Validator(w http.ResponseWriter, r *http.Request) {
if validatorPageData.CappellaHasHappened {
// if we are currently past the cappella fork epoch, we can calculate the withdrawal information

// get validator withdrawals
withdrawalsCount, lastWithdrawalsEpoch, err := db.GetValidatorWithdrawalsCount(validatorPageData.Index)
validatorSlice := []uint64{index}
withdrawalsCount, err := db.GetTotalWithdrawalsCount(validatorSlice)
if err != nil {
return fmt.Errorf("error getting validator withdrawals count from db: %w", err)
}
validatorPageData.WithdrawalCount = withdrawalsCount
lastWithdrawalsEpochs, err := db.GetLastWithdrawalEpoch(validatorSlice)
if err != nil {
return fmt.Errorf("error getting validator last withdrawal epoch from db: %w", err)
}
lastWithdrawalsEpoch := lastWithdrawalsEpochs[index]

blsChange, err := db.GetValidatorBLSChange(validatorPageData.Index)
if err != nil {
Expand Down Expand Up @@ -1229,7 +1234,7 @@ func ValidatorWithdrawals(w http.ResponseWriter, r *http.Request) {

length := uint64(10)

withdrawalCount, _, err := db.GetValidatorWithdrawalsCount(index)
withdrawalCount, err := db.GetTotalWithdrawalsCount([]uint64{index})
if err != nil {
logger.Errorf("error retrieving validator withdrawals count: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
Expand Down

0 comments on commit f0a817e

Please sign in to comment.