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.
655647func validatorFromRow (rows pgx.Rows ) (* chaindb.Validator , error ) {
656648 var publicKey []byte
0 commit comments