Skip to content

Commit c14183c

Browse files
committed
Do not store validators 0 balances.
1 parent c863f9b commit c14183c

5 files changed

Lines changed: 90 additions & 87 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
0.8.9:
2+
- do not store 0 validator balances (i.e. from validators that are not active, or have exited and withdrawn)
3+
14
0.8.8:
25
- handle upstream change of epoch serialization for metadata
36
- do not error on deposit transactions containing events without topics

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ import (
6464
)
6565

6666
// ReleaseVersion is the release version for the code.
67-
var ReleaseVersion = "0.8.8"
67+
var ReleaseVersion = "0.8.9"
6868

6969
func main() {
7070
os.Exit(main2())

services/chaindb/postgresql/validators.go

Lines changed: 78 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright © 2020 - 2023 Weald Technology Trading.
1+
// Copyright © 2020 - 2025 Weald Technology Trading.
22
// Licensed under the Apache License, Version 2.0 (the "License");
33
// you may not use this file except in compliance with the License.
44
// You may obtain a copy of the License at
@@ -380,14 +380,25 @@ func (s *Service) ValidatorBalancesByEpoch(
380380
tx = s.tx(ctx)
381381
}
382382

383+
// The database does not store balances for 0-balance validators, so join the balances with the validator table to ensure we
384+
// have an returned row for every validator.
383385
rows, err := tx.Query(ctx, `
384-
SELECT f_validator_index
385-
,f_epoch
386-
,f_balance
387-
,f_effective_balance
388-
FROM t_validator_balances
389-
WHERE f_epoch = $1::BIGINT
390-
ORDER BY f_validator_index`,
386+
WITH v_validator_balances AS (
387+
SELECT f_validator_index
388+
,f_epoch
389+
,f_balance
390+
,f_effective_balance
391+
FROM t_validator_balances
392+
WHERE f_epoch = $1
393+
ORDER BY f_validator_index)
394+
SELECT f_index
395+
,COALESCE(f_epoch, $1) AS f_epoch
396+
,COALESCE(f_balance, 0) AS f_balance
397+
,COALESCE(v_validator_balances.f_effective_balance, 0) AS f_effective_balance
398+
FROM t_validators
399+
LEFT JOIN v_validator_balances
400+
ON t_validators.f_index = v_validator_balances.f_validator_index
401+
ORDER BY f_index`,
391402
uint64(epoch),
392403
)
393404
if err != nil {
@@ -402,9 +413,10 @@ func (s *Service) ValidatorBalancesByEpoch(
402413
if err != nil {
403414
return nil, errors.Wrap(err, "failed to scan row")
404415
}
416+
405417
validatorBalances = append(validatorBalances, validatorBalance)
406418
if uint64(validatorBalance.Index) != uint64(len(validatorBalances)-1) {
407-
panic(fmt.Sprintf("bad index %d with len %d", validatorBalance.Index, len(validatorBalances)))
419+
panic(fmt.Sprintf("bad index %d with len %d at epoch %d", validatorBalance.Index, len(validatorBalances), epoch))
408420
}
409421
}
410422

@@ -437,15 +449,26 @@ func (s *Service) ValidatorBalancesByIndexAndEpoch(
437449
tx = s.tx(ctx)
438450
}
439451

452+
// The database does not store balances for 0-balance validators, so join the balances with the validator table to ensure we
453+
// have an returned row for every validator.
440454
rows, err := tx.Query(ctx, `
441-
SELECT f_validator_index
442-
,f_epoch
443-
,f_balance
444-
,f_effective_balance
445-
FROM t_validator_balances
446-
WHERE f_epoch = $2::BIGINT
447-
AND f_validator_index = ANY($1)
448-
ORDER BY f_validator_index`,
455+
WITH v_validator_balances AS(
456+
SELECT f_validator_index
457+
,f_epoch
458+
,f_balance
459+
,f_effective_balance
460+
FROM t_validator_balances
461+
WHERE f_epoch = $2
462+
AND f_validator_index = ANY($1))
463+
SELECT f_index
464+
,COALESCE(f_epoch, $2) AS f_epoch
465+
,COALESCE(f_balance, 0) AS f_balance
466+
,COALESCE(v_validator_balances.f_effective_balance, 0) AS f_effective_balance
467+
FROM t_validators
468+
LEFT JOIN v_validator_balances
469+
ON t_validators.f_index = v_validator_balances.f_validator_index
470+
WHERE t_validators.f_index = ANY($1)
471+
ORDER BY f_index`,
449472
validatorIndices,
450473
uint64(epoch),
451474
)
@@ -501,27 +524,28 @@ func (s *Service) ValidatorBalancesByIndexAndEpochRange(
501524
return validatorIndices[i] < validatorIndices[j]
502525
})
503526

504-
// Create an array for the validator indices. This gives us higher performance for our query.
505-
indices := make([]string, len(validatorIndices))
506-
for i, validatorIndex := range validatorIndices {
507-
indices[i] = fmt.Sprintf("(%d)", validatorIndex)
527+
// Create a matrix of the values we require. This allows the database to fill in the blanks when it doesn't have a balance for
528+
// the required (index,epoch) tuple (for exmple when the balance is 0).
529+
values := make([]string, 0)
530+
for _, validatorIndex := range validatorIndices {
531+
for epoch := startEpoch; epoch < endEpoch; epoch++ {
532+
values = append(values, fmt.Sprintf("(%d,%d)", validatorIndex, epoch))
533+
}
508534
}
509535

510536
rows, err := tx.Query(ctx, fmt.Sprintf(`
511-
SELECT f_validator_index
512-
,f_epoch
513-
,f_balance
514-
,f_effective_balance
515-
FROM t_validator_balances
516-
JOIN (VALUES %s)
517-
AS x(id)
518-
ON x.id = t_validator_balances.f_validator_index
519-
WHERE f_epoch >= $1
520-
AND f_epoch < $2
521-
ORDER BY f_validator_index
522-
,f_epoch`, strings.Join(indices, ",")),
523-
uint64(startEpoch),
524-
uint64(endEpoch),
537+
WITH v_validator_epochs(f_validator_index, f_epoch) AS(VALUES %s)
538+
SELECT v_validator_epochs.f_validator_index
539+
,v_validator_epochs.f_epoch
540+
,COALESCE(f_balance, 0) AS f_balance
541+
,COALESCE(f_effective_balance, 0) AS f_effective_balance
542+
FROM v_validator_epochs
543+
LEFT JOIN t_validator_balances
544+
ON t_validator_balances.f_validator_index = v_validator_epochs.f_validator_index
545+
AND t_validator_balances.f_epoch = v_validator_epochs.f_epoch
546+
ORDER BY f_validator_index
547+
,f_epoch`,
548+
strings.Join(values, ",")),
525549
)
526550
if err != nil {
527551
return nil, err
@@ -541,12 +565,6 @@ func (s *Service) ValidatorBalancesByIndexAndEpochRange(
541565
validatorBalances[validatorBalance.Index] = append(validatorBalances[validatorBalance.Index], validatorBalance)
542566
}
543567

544-
// If a validator is not present until after the beginning of the range, for example we ask for epochs 5->10 and
545-
// the validator is first present at epoch 7, we need to front-pad the data for that validator with 0s.
546-
if err := padValidatorBalances(validatorBalances, int(uint64(endEpoch)-uint64(startEpoch)), startEpoch); err != nil {
547-
return nil, err
548-
}
549-
550568
return validatorBalances, nil
551569
}
552570

@@ -581,29 +599,28 @@ func (s *Service) ValidatorBalancesByIndexAndEpochs(
581599
return validatorIndices[i] < validatorIndices[j]
582600
})
583601

584-
// Create an array for the validator indices. This gives us higher performance for our query.
585-
indices := make([]string, len(validatorIndices))
586-
for i, validatorIndex := range validatorIndices {
587-
indices[i] = fmt.Sprintf("(%d)", validatorIndex)
602+
// Create a matrix of the values we require. This allows the database to fill in the blanks when it doesn't have a balance for
603+
// the required (index,epoch) tuple (for exmple when the balance is 0).
604+
values := make([]string, 0)
605+
for _, validatorIndex := range validatorIndices {
606+
for _, epoch := range epochs {
607+
values = append(values, fmt.Sprintf("(%d,%d)", validatorIndex, epoch))
608+
}
588609
}
589610

590-
dbEpochs := make([]uint64, len(epochs))
591-
for i, epoch := range epochs {
592-
dbEpochs[i] = uint64(epoch)
593-
}
594611
rows, err := tx.Query(ctx, fmt.Sprintf(`
595-
SELECT f_validator_index
596-
,f_epoch
597-
,f_balance
598-
,f_effective_balance
599-
FROM t_validator_balances
600-
JOIN (VALUES %s)
601-
AS x(id)
602-
ON x.id = t_validator_balances.f_validator_index
603-
WHERE f_epoch = ANY($1)
604-
ORDER BY f_validator_index
605-
,f_epoch`, strings.Join(indices, ",")),
606-
dbEpochs,
612+
WITH v_validator_epochs(f_validator_index, f_epoch) AS(VALUES %s)
613+
SELECT v_validator_epochs.f_validator_index
614+
,v_validator_epochs.f_epoch
615+
,COALESCE(f_balance, 0) AS f_balance
616+
,COALESCE(f_effective_balance, 0) AS f_effective_balance
617+
FROM v_validator_epochs
618+
LEFT JOIN t_validator_balances
619+
ON t_validator_balances.f_validator_index = v_validator_epochs.f_validator_index
620+
AND t_validator_balances.f_epoch = v_validator_epochs.f_epoch
621+
ORDER BY f_validator_index
622+
,f_epoch`,
623+
strings.Join(values, ",")),
607624
)
608625
if err != nil {
609626
return nil, err
@@ -626,31 +643,6 @@ func (s *Service) ValidatorBalancesByIndexAndEpochs(
626643
return validatorBalances, nil
627644
}
628645

629-
func padValidatorBalances(validatorBalances map[phase0.ValidatorIndex][]*chaindb.ValidatorBalance, entries int, startEpoch phase0.Epoch) error {
630-
for validatorIndex, balances := range validatorBalances {
631-
if len(balances) != entries {
632-
paddedBalances := make([]*chaindb.ValidatorBalance, entries)
633-
padding := entries - len(balances)
634-
for i := range padding {
635-
paddedBalances[i] = &chaindb.ValidatorBalance{
636-
Index: validatorIndex,
637-
Epoch: startEpoch + phase0.Epoch(i),
638-
Balance: 0,
639-
EffectiveBalance: 0,
640-
}
641-
}
642-
if len(balances) > 0 && balances[0].Epoch != startEpoch+phase0.Epoch(padding) {
643-
return fmt.Errorf("data missing in chaindb for validator %d", validatorIndex)
644-
}
645-
646-
copy(paddedBalances[padding:], balances)
647-
validatorBalances[validatorIndex] = paddedBalances
648-
}
649-
}
650-
651-
return nil
652-
}
653-
654646
// validatorFromRow converts a SQL row in to a validator.
655647
func validatorFromRow(rows pgx.Rows) (*chaindb.Validator, error) {
656648
var publicKey []byte

services/finalizer/standard/handler.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ func (s *Service) updateCanonicalBlocks(ctx context.Context, root phase0.Root) e
191191
return errors.Wrap(err, "failed to obtain block")
192192
}
193193

194+
if block == nil {
195+
return fmt.Errorf("unable to obtain block for root %#x", root)
196+
}
197+
194198
s.log.Trace().Uint64("slot", uint64(block.Slot)).Msg("Canonicalizing up to slot")
195199

196200
if err := s.canonicalizeBlocks(ctx, root, phase0.Slot(md.LatestCanonicalSlot)); err != nil {

services/validators/standard/handler.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ func (s *Service) onEpochTransitionValidatorBalancesForEpoch(ctx context.Context
202202
if s.balances {
203203
dbValidatorBalances := make([]*chaindb.ValidatorBalance, 0, len(validators))
204204
for index, validator := range validators {
205+
// Do not store 0 balances.
206+
if validator.Balance == 0 {
207+
continue
208+
}
205209
dbValidatorBalances = append(dbValidatorBalances, &chaindb.ValidatorBalance{
206210
Index: index,
207211
Epoch: epoch,

0 commit comments

Comments
 (0)