Skip to content

Commit 43e5f35

Browse files
committed
perf: batch fetch estimates
1 parent 8e5ae6b commit 43e5f35

File tree

2 files changed

+91
-66
lines changed

2 files changed

+91
-66
lines changed

backend/pkg/api/data_access/validators.go

Lines changed: 72 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -140,91 +140,107 @@ func (d *DataAccessService) GetValidatorsByGraffiti(ctx context.Context, graffit
140140
return runQueryRows[[]t.VDBValidator](ctx, d.readerDb, validatorsDs)
141141
}
142142

143-
// estimate activation for pending and deposited validators
144-
// TODO support estimates for validators without index
145-
func (d *DataAccessService) getValidatorActivation(ctx context.Context, validator uint64) (uint64, error) {
143+
// fills the activation epochs for a set of validators. Tries to estimate pending validators based on current queue simulation
144+
func (d *DataAccessService) getValidatorActivationEpochs(ctx context.Context, validators map[uint64]*uint64) error {
146145
validatorMapping, err := d.services.GetCurrentValidatorMapping()
147146
if err != nil {
148-
return 0, err
147+
return err
149148
}
150-
if validator >= uint64(len(validatorMapping.ValidatorMetadata)) {
151-
return 0, fmt.Errorf("validator index %d not found in validator mapping", validator)
152-
}
153-
metadata := validatorMapping.ValidatorMetadata[validator]
154-
if metadata.ActivationEpoch.Valid {
155-
return uint64(metadata.ActivationEpoch.Int64), nil
156-
}
157-
158-
// estimate
159149
latestEpoch := cache.LatestFinalizedEpoch.Get()
160-
if d.config.ClConfig.ElectraForkEpoch > latestEpoch {
161-
if constypes.ValidatorDbStatus(metadata.Status) != constypes.DbPending {
162-
// probably not enough deposits yet
163-
return 0, fmt.Errorf("validator %d is not pending activation yet", validator)
164-
}
165-
if !metadata.Queues.ActivationIndex.Valid {
166-
return 0, fmt.Errorf("validator %d has no activation index", validator)
150+
latestStats := cache.LatestStats.Get()
151+
activationChurnRate := uint64(4)
152+
if latestStats.ValidatorActivationChurnLimit == nil {
153+
log.Warnf("Activation Churn rate not set in config, using 4 as default")
154+
} else {
155+
activationChurnRate = *latestStats.ValidatorActivationChurnLimit
156+
}
157+
pendingValidatorsPostPectra := make([]uint64, 0)
158+
159+
for validator := range validators {
160+
if validator >= uint64(len(validatorMapping.ValidatorMetadata)) {
161+
return fmt.Errorf("validator index %d not found in validator mapping", validator)
167162
}
168-
queuePosition := uint64(metadata.Queues.ActivationIndex.Int64)
169-
170-
latestStats := cache.LatestStats.Get()
171-
activationChurnRate := uint64(4)
172-
if latestStats.ValidatorActivationChurnLimit == nil {
173-
log.Warnf("Activation Churn rate not set in config, using 4 as default")
174-
} else {
175-
activationChurnRate = *latestStats.ValidatorActivationChurnLimit
163+
metadata := validatorMapping.ValidatorMetadata[validator]
164+
if metadata.ActivationEpoch.Valid {
165+
// validator already activated, easy case
166+
activationEpoch := uint64(metadata.ActivationEpoch.Int64)
167+
validators[validator] = &activationEpoch
168+
continue
176169
}
177170

178-
epochsToWait := (queuePosition - 1) / activationChurnRate
179-
// calculate dequeue epoch
180-
estimatedActivationEpoch := latestEpoch + epochsToWait + 1
181-
// add activation offset
182-
estimatedActivationEpoch += utils.Config.Chain.ClConfig.MaxSeedLookahead + 1
171+
// estimate
172+
if d.config.ClConfig.ElectraForkEpoch > latestEpoch {
173+
// pre pectra
174+
if constypes.ValidatorDbStatus(metadata.Status) != constypes.DbPending {
175+
// probably not enough deposits yet
176+
continue
177+
}
178+
if !metadata.Queues.ActivationIndex.Valid {
179+
// could support estimates for validators without index
180+
continue
181+
}
182+
queuePosition := uint64(metadata.Queues.ActivationIndex.Int64)
183183

184-
return estimatedActivationEpoch, nil
185-
}
184+
epochsToWait := (queuePosition - 1) / activationChurnRate
185+
// calculate dequeue epoch
186+
estimatedActivationEpoch := latestEpoch + epochsToWait + 1
187+
// add activation offset
188+
estimatedActivationEpoch += utils.Config.Chain.ClConfig.MaxSeedLookahead + 1
186189

187-
// post pectra
188-
if constypes.ValidatorDbStatus(metadata.Status) != constypes.DbDeposited {
189-
// should not happen since there's no more activation queue after deposits have been processed (see process_registry_updates)
190-
return 0, fmt.Errorf("validator %d has no activation epoch", validator)
190+
validators[validator] = &estimatedActivationEpoch
191+
continue
192+
}
193+
194+
// post pectra
195+
if constypes.ValidatorDbStatus(metadata.Status) != constypes.DbDeposited {
196+
// should not happen since there's no more activation queue after deposits have been processed (see process_registry_updates)
197+
return fmt.Errorf("validator %d has no activation epoch", validator)
198+
}
199+
pendingValidatorsPostPectra = append(pendingValidatorsPostPectra, validator)
191200
}
201+
192202
// determine sum of previous deposit
193203
// check if there's a pending deposit which pushes above min activation
194204
// return estimate of that from db
195205

196206
// could also simulate in db, but wouldn't be pretty
197207
ds := goqu.Dialect("postgres").From("pending_deposits_queue").
198208
Select(
209+
goqu.I("validator_index"),
199210
goqu.I("est_clear_epoch"),
200211
goqu.I("amount"),
201212
).
202213
Where(
203-
goqu.I("validator_index").Eq(validator),
214+
goqu.I("validator_index").In(pendingValidatorsPostPectra),
204215
).
205216
Order(goqu.I("id").Asc())
206217

207218
type dbResult struct {
208-
ClearEpoch uint64 `db:"est_clear_epoch"`
209-
Amount uint64 `db:"amount"`
219+
ValidatorIndex uint64 `db:"validator_index"`
220+
ClearEpoch uint64 `db:"est_clear_epoch"`
221+
Amount uint64 `db:"amount"`
210222
}
211-
results, err := runQueryRows[[]dbResult](ctx, d.alloyReader, ds)
223+
results, err := runQueryRows[[]dbResult](ctx, d.readerDb, ds)
212224
if err != nil {
213-
return 0, err
214-
}
215-
216-
effectiveBalance := metadata.EffectiveBalance
217-
balance := metadata.Balance
218-
upwardThreshold := utils.Config.Chain.ClConfig.EffectiveBalanceIncrement / utils.Config.Chain.ClConfig.HysteresisQuotient * utils.Config.Chain.ClConfig.HysteresisUpwardMultiplier
219-
for _, deposit := range results {
220-
balance += deposit.Amount
221-
if effectiveBalance+upwardThreshold < balance {
222-
effectiveBalance = balance - balance%utils.Config.Chain.ClConfig.EffectiveBalanceIncrement
223-
if effectiveBalance >= utils.Config.Chain.ClConfig.MinActivationBalance {
224-
return deposit.ClearEpoch, nil
225+
return err
226+
}
227+
228+
for _, result := range results {
229+
metadata := validatorMapping.ValidatorMetadata[result.ValidatorIndex]
230+
effectiveBalance := metadata.EffectiveBalance
231+
balance := metadata.Balance
232+
upwardThreshold := utils.Config.Chain.ClConfig.EffectiveBalanceIncrement / utils.Config.Chain.ClConfig.HysteresisQuotient * utils.Config.Chain.ClConfig.HysteresisUpwardMultiplier
233+
for _, deposit := range results {
234+
balance += deposit.Amount
235+
if effectiveBalance+upwardThreshold < balance {
236+
effectiveBalance = balance - balance%utils.Config.Chain.ClConfig.EffectiveBalanceIncrement
237+
if effectiveBalance >= utils.Config.Chain.ClConfig.MinActivationBalance {
238+
validators[deposit.ValidatorIndex] = &deposit.ClearEpoch
239+
break
240+
}
225241
}
226242
}
227243
}
228244

229-
return 0, fmt.Errorf("validator %d has not enough pending ETH deposits", validator)
245+
return nil
230246
}

backend/pkg/api/data_access/vdb_summary.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,7 @@ func (d *DataAccessService) GetValidatorDashboardSummaryValidators(ctx context.C
10711071
}
10721072

10731073
// Fill the data
1074+
validatorsToFetchActivation := make(map[uint64]*uint64, 0)
10741075
for _, validatorIndex := range validatorIndices {
10751076
metadata := validatorMapping.ValidatorMetadata[validatorIndex]
10761077

@@ -1079,16 +1080,7 @@ func (d *DataAccessService) GetValidatorDashboardSummaryValidators(ctx context.C
10791080
// could add activation epoch estimate if possible
10801081
result.Deposited = append(result.Deposited, validatorIndex)
10811082
case constypes.DbPending:
1082-
validatorInfo := t.IndexTimestamp{
1083-
Index: validatorIndex,
1084-
}
1085-
activationEpoch, err := d.getValidatorActivation(ctx, validatorIndex)
1086-
if err != nil {
1087-
log.Warnf("error getting validator activation: %v", err)
1088-
} else {
1089-
validatorInfo.Timestamp = uint64(utils.EpochToTime(activationEpoch).Unix())
1090-
}
1091-
result.Pending = append(result.Pending, validatorInfo)
1083+
validatorsToFetchActivation[validatorIndex] = nil
10921084
case constypes.DbActiveOnline:
10931085
result.Online = append(result.Online, validatorIndex)
10941086
case constypes.DbActiveOffline:
@@ -1141,6 +1133,23 @@ func (d *DataAccessService) GetValidatorDashboardSummaryValidators(ctx context.C
11411133
}
11421134
}
11431135

1136+
// Get the activation epoch for pending validators
1137+
err = d.getValidatorActivationEpochs(ctx, validatorsToFetchActivation)
1138+
if err != nil {
1139+
return nil, err
1140+
}
1141+
for validatorIndex, activationEpoch := range validatorsToFetchActivation {
1142+
if activationEpoch == nil {
1143+
continue
1144+
}
1145+
1146+
validatorInfo := t.IndexTimestamp{
1147+
Index: validatorIndex,
1148+
Timestamp: uint64(utils.EpochToTime(*activationEpoch).Unix()),
1149+
}
1150+
result.Pending = append(result.Pending, validatorInfo)
1151+
}
1152+
11441153
return result, nil
11451154
}
11461155

0 commit comments

Comments
 (0)