Skip to content

Commit 4600d4c

Browse files
authored
(BEDS-500) don't throw error when adding more validators than limit allows (#877)
1 parent 7d35b43 commit 4600d4c

File tree

4 files changed

+129
-185
lines changed

4 files changed

+129
-185
lines changed

backend/pkg/api/data_access/dummy.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,6 @@ func (d *DummyService) GetValidatorDashboardGroupExists(ctx context.Context, das
253253
return true, nil
254254
}
255255

256-
func (d *DummyService) GetValidatorDashboardExistingValidatorCount(ctx context.Context, dashboardId t.VDBIdPrimary, validators []t.VDBValidator) (uint64, error) {
257-
return getDummyData[uint64]()
258-
}
259-
260256
func (d *DummyService) AddValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, validators []t.VDBValidator) ([]t.VDBPostValidatorsData, error) {
261257
return getDummyData[[]t.VDBPostValidatorsData]()
262258
}

backend/pkg/api/data_access/vdb.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ type ValidatorDashboardRepository interface {
2828
GetValidatorDashboardGroupCount(ctx context.Context, dashboardId t.VDBIdPrimary) (uint64, error)
2929
GetValidatorDashboardGroupExists(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64) (bool, error)
3030

31-
GetValidatorDashboardExistingValidatorCount(ctx context.Context, dashboardId t.VDBIdPrimary, validators []t.VDBValidator) (uint64, error)
3231
AddValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, validators []t.VDBValidator) ([]t.VDBPostValidatorsData, error)
3332
AddValidatorDashboardValidatorsByDepositAddress(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, address string, limit uint64) ([]t.VDBPostValidatorsData, error)
3433
AddValidatorDashboardValidatorsByWithdrawalAddress(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, address string, limit uint64) ([]t.VDBPostValidatorsData, error)

backend/pkg/api/data_access/vdb_management.go

Lines changed: 98 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/hex"
77
"fmt"
88
"math/big"
9+
"slices"
910
"sort"
1011
"strconv"
1112
"strings"
@@ -15,7 +16,6 @@ import (
1516
"github.com/ethereum/go-ethereum/common/hexutil"
1617
"github.com/gobitfly/beaconchain/pkg/api/enums"
1718
t "github.com/gobitfly/beaconchain/pkg/api/types"
18-
"github.com/gobitfly/beaconchain/pkg/commons/log"
1919
"github.com/gobitfly/beaconchain/pkg/commons/utils"
2020
constypes "github.com/gobitfly/beaconchain/pkg/consapi/types"
2121
"github.com/lib/pq"
@@ -790,21 +790,6 @@ func (d *DataAccessService) GetValidatorDashboardGroupExists(ctx context.Context
790790
return groupExists, err
791791
}
792792

793-
// return how many of the passed validators are already in the dashboard
794-
func (d *DataAccessService) GetValidatorDashboardExistingValidatorCount(ctx context.Context, dashboardId t.VDBIdPrimary, validators []t.VDBValidator) (uint64, error) {
795-
if len(validators) == 0 {
796-
return 0, nil
797-
}
798-
799-
var count uint64
800-
err := d.alloyReader.GetContext(ctx, &count, `
801-
SELECT COUNT(*)
802-
FROM users_val_dashboards_validators
803-
WHERE dashboard_id = $1 AND validator_index = ANY($2)
804-
`, dashboardId, pq.Array(validators))
805-
return count, err
806-
}
807-
808793
func (d *DataAccessService) AddValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, validators []t.VDBValidator) ([]t.VDBPostValidatorsData, error) {
809794
if len(validators) == 0 {
810795
// No validators to add
@@ -889,191 +874,145 @@ func (d *DataAccessService) AddValidatorDashboardValidators(ctx context.Context,
889874
return result, nil
890875
}
891876

877+
// Updates the group for validators already in the dashboard linked to the deposit address.
878+
// Adds up to limit new validators associated with the deposit address, if not already in the dashboard.
892879
func (d *DataAccessService) AddValidatorDashboardValidatorsByDepositAddress(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, address string, limit uint64) ([]t.VDBPostValidatorsData, error) {
893-
// for all validators already in the dashboard that are associated with the deposit address, update the group
894-
// then add no more than `limit` validators associated with the deposit address to the dashboard
895880
addressParsed, err := hex.DecodeString(strings.TrimPrefix(address, "0x"))
896881
if err != nil {
897882
return nil, err
898883
}
899884

900-
if len(addressParsed) != 20 {
901-
return nil, fmt.Errorf("invalid deposit address: %s", address)
902-
}
903-
var validatorIndicesToAdd []uint64
904-
err = d.readerDb.SelectContext(ctx, &validatorIndicesToAdd, "SELECT validatorindex FROM validators WHERE pubkey IN (SELECT publickey FROM eth1_deposits WHERE from_address = $1) ORDER BY validatorindex LIMIT $2;", addressParsed, limit)
905-
if err != nil {
906-
return nil, err
907-
}
885+
g, gCtx := errgroup.WithContext(ctx)
908886

909-
// retrieve the existing validators
910-
var existingValidators []uint64
911-
err = d.alloyWriter.SelectContext(ctx, &existingValidators, "SELECT validator_index FROM users_val_dashboards_validators WHERE dashboard_id = $1", dashboardId)
912-
if err != nil {
913-
return nil, err
914-
}
915-
existingValidatorsMap := make(map[uint64]bool, len(existingValidators))
916-
for _, validatorIndex := range existingValidators {
917-
existingValidatorsMap[validatorIndex] = true
918-
}
919-
920-
// filter out the validators that are already in the dashboard
887+
// fetch validators that are already in the dashboard and associated with the deposit address
921888
var validatorIndicesToUpdate []uint64
889+
890+
g.Go(func() error {
891+
return d.readerDb.SelectContext(gCtx, &validatorIndicesToUpdate, `
892+
SELECT DISTINCT uvdv.validator_index
893+
FROM validators v
894+
JOIN eth1_deposits d ON v.pubkey = d.publickey
895+
JOIN users_val_dashboards_validators uvdv ON v.validatorindex = uvdv.validator_index
896+
WHERE uvdv.dashboard_id = $1 AND d.from_address = $2;
897+
`, dashboardId, addressParsed)
898+
})
899+
900+
// fetch validators that are not yet in the dashboard and associated with the deposit address, up to the limit
922901
var validatorIndicesToInsert []uint64
923-
for _, validatorIndex := range validatorIndicesToAdd {
924-
if _, ok := existingValidatorsMap[validatorIndex]; ok {
925-
validatorIndicesToUpdate = append(validatorIndicesToUpdate, validatorIndex)
926-
} else {
927-
validatorIndicesToInsert = append(validatorIndicesToInsert, validatorIndex)
928-
}
929-
}
902+
g.Go(func() error {
903+
return d.readerDb.SelectContext(gCtx, &validatorIndicesToInsert, `
904+
SELECT DISTINCT v.validatorindex
905+
FROM validators v
906+
JOIN eth1_deposits d ON v.pubkey = d.publickey
907+
LEFT JOIN users_val_dashboards_validators uvdv ON v.validatorindex = uvdv.validator_index AND uvdv.dashboard_id = $1
908+
WHERE d.from_address = $2 AND uvdv.validator_index IS NULL
909+
ORDER BY v.validatorindex
910+
LIMIT $3;
911+
`, dashboardId, addressParsed, limit)
912+
})
930913

931-
// update the group for all existing validators
932-
validatorIndices := make([]uint64, 0, int(limit))
933-
validatorIndices = append(validatorIndices, validatorIndicesToUpdate...)
934-
935-
// insert the new validators up to the allowed user max limit taking into account how many validators are already in the dashboard
936-
if len(validatorIndicesToInsert) > 0 {
937-
freeSpace := int(limit) - len(existingValidators)
938-
if freeSpace > 0 {
939-
if len(validatorIndicesToInsert) > freeSpace { // cap inserts to the amount of free space available
940-
log.Infof("limiting the number of validators to insert to %d", freeSpace)
941-
validatorIndicesToInsert = validatorIndicesToInsert[:freeSpace]
942-
}
943-
validatorIndices = append(validatorIndices, validatorIndicesToInsert...)
944-
}
914+
err = g.Wait()
915+
if err != nil {
916+
return nil, err
945917
}
946918

947-
if len(validatorIndices) == 0 {
948-
// no validators to add
949-
return []t.VDBPostValidatorsData{}, nil
950-
}
951-
log.Infof("inserting %d new validators and updating %d validators of dashboard %d, limit is %d", len(validatorIndicesToInsert), len(validatorIndicesToUpdate), dashboardId, limit)
919+
validatorIndices := slices.Concat(validatorIndicesToUpdate, validatorIndicesToInsert)
920+
952921
return d.AddValidatorDashboardValidators(ctx, dashboardId, groupId, validatorIndices)
953922
}
954923

924+
// Updates the group for validators already in the dashboard linked to the withdrawal address.
925+
// Adds up to limit new validators associated with the withdrawal address, if not already in the dashboard.
955926
func (d *DataAccessService) AddValidatorDashboardValidatorsByWithdrawalAddress(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, address string, limit uint64) ([]t.VDBPostValidatorsData, error) {
956-
// for all validators already in the dashboard that are associated with the withdrawal address, update the group
957-
// then add no more than `limit` validators associated with the deposit address to the dashboard
958927
addressParsed, err := hex.DecodeString(strings.TrimPrefix(address, "0x"))
959928
if err != nil {
960929
return nil, err
961930
}
962-
var validatorIndicesToAdd []uint64
963-
err = d.readerDb.SelectContext(ctx, &validatorIndicesToAdd, "SELECT validatorindex FROM validators WHERE withdrawalcredentials = $1 ORDER BY validatorindex LIMIT $2;", addressParsed, limit)
964-
if err != nil {
965-
return nil, err
966-
}
967931

968-
// retrieve the existing validators
969-
var existingValidators []uint64
970-
err = d.alloyWriter.SelectContext(ctx, &existingValidators, "SELECT validator_index FROM users_val_dashboards_validators WHERE dashboard_id = $1", dashboardId)
971-
if err != nil {
972-
return nil, err
973-
}
974-
existingValidatorsMap := make(map[uint64]bool, len(existingValidators))
975-
for _, validatorIndex := range existingValidators {
976-
existingValidatorsMap[validatorIndex] = true
977-
}
932+
g, gCtx := errgroup.WithContext(ctx)
978933

979-
// filter out the validators that are already in the dashboard
934+
// fetch validators that are already in the dashboard and associated with the withdrawal address
980935
var validatorIndicesToUpdate []uint64
936+
g.Go(func() error {
937+
return d.readerDb.SelectContext(gCtx, &validatorIndicesToUpdate, `
938+
SELECT DISTINCT uvdv.validator_index
939+
FROM validators v
940+
JOIN users_val_dashboards_validators uvdv ON v.validatorindex = uvdv.validator_index
941+
WHERE uvdv.dashboard_id = $1 AND v.withdrawalcredentials = $2 AND uvdv.dashboard_id = $2;
942+
`, dashboardId, addressParsed)
943+
})
944+
945+
// fetch validators that are not yet in the dashboard and associated with the withdrawal address, up to the limit
981946
var validatorIndicesToInsert []uint64
982-
for _, validatorIndex := range validatorIndicesToAdd {
983-
if _, ok := existingValidatorsMap[validatorIndex]; ok {
984-
validatorIndicesToUpdate = append(validatorIndicesToUpdate, validatorIndex)
985-
} else {
986-
validatorIndicesToInsert = append(validatorIndicesToInsert, validatorIndex)
987-
}
988-
}
947+
g.Go(func() error {
948+
return d.readerDb.SelectContext(gCtx, &validatorIndicesToInsert, `
949+
SELECT DISTINCT v.validatorindex
950+
FROM validators v
951+
LEFT JOIN users_val_dashboards_validators uvdv ON v.validatorindex = uvdv.validator_index AND uvdv.dashboard_id = $1
952+
WHERE v.withdrawalcredentials = $2 AND uvdv.validator_index IS NULL
953+
ORDER BY v.validatorindex
954+
LIMIT $3;
955+
`, dashboardId, addressParsed, limit)
956+
})
989957

990-
// update the group for all existing validators
991-
validatorIndices := make([]uint64, 0, int(limit))
992-
validatorIndices = append(validatorIndices, validatorIndicesToUpdate...)
993-
994-
// insert the new validators up to the allowed user max limit taking into account how many validators are already in the dashboard
995-
if len(validatorIndicesToInsert) > 0 {
996-
freeSpace := int(limit) - len(existingValidators)
997-
if freeSpace > 0 {
998-
if len(validatorIndicesToInsert) > freeSpace { // cap inserts to the amount of free space available
999-
log.Infof("limiting the number of validators to insert to %d", freeSpace)
1000-
validatorIndicesToInsert = validatorIndicesToInsert[:freeSpace]
1001-
}
1002-
validatorIndices = append(validatorIndices, validatorIndicesToInsert...)
1003-
}
958+
err = g.Wait()
959+
if err != nil {
960+
return nil, err
1004961
}
1005962

1006-
if len(validatorIndices) == 0 {
1007-
// no validators to add
1008-
return []t.VDBPostValidatorsData{}, nil
1009-
}
1010-
log.Infof("inserting %d new validators and updating %d validators of dashboard %d, limit is %d", len(validatorIndicesToInsert), len(validatorIndicesToUpdate), dashboardId, limit)
963+
validatorIndices := slices.Concat(validatorIndicesToUpdate, validatorIndicesToInsert)
964+
1011965
return d.AddValidatorDashboardValidators(ctx, dashboardId, groupId, validatorIndices)
1012966
}
1013967

968+
// Update the group for validators already in the dashboard linked to the graffiti (via produced block).
969+
// Add up to limit new validators associated with the graffiti, if not already in the dashboard.
1014970
func (d *DataAccessService) AddValidatorDashboardValidatorsByGraffiti(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, graffiti string, limit uint64) ([]t.VDBPostValidatorsData, error) {
1015-
// for all validators already in the dashboard that are associated with the graffiti (by produced block), update the group
1016-
// then add no more than `limit` validators associated with the deposit address to the dashboard
1017-
var validatorIndicesToAdd []uint64
1018-
err := d.readerDb.SelectContext(ctx, &validatorIndicesToAdd, "SELECT DISTINCT proposer FROM blocks WHERE graffiti_text = $1 ORDER BY proposer LIMIT $2;", graffiti, limit)
1019-
if err != nil {
1020-
return nil, err
1021-
}
971+
g, gCtx := errgroup.WithContext(ctx)
1022972

1023-
// retrieve the existing validators
1024-
var existingValidators []uint64
1025-
err = d.alloyWriter.SelectContext(ctx, &existingValidators, "SELECT validator_index FROM users_val_dashboards_validators WHERE dashboard_id = $1", dashboardId)
1026-
if err != nil {
1027-
return nil, err
1028-
}
1029-
existingValidatorsMap := make(map[uint64]bool, len(existingValidators))
1030-
for _, validatorIndex := range existingValidators {
1031-
existingValidatorsMap[validatorIndex] = true
1032-
}
1033-
1034-
// filter out the validators that are already in the dashboard
973+
// fetch validators that are already in the dashboard and associated with the graffiti
1035974
var validatorIndicesToUpdate []uint64
975+
g.Go(func() error {
976+
return d.readerDb.SelectContext(gCtx, &validatorIndicesToUpdate, `
977+
SELECT DISTINCT uvdv.validator_index
978+
FROM blocks b
979+
JOIN users_val_dashboards_validators uvdv ON b.proposer = uvdv.validator_index
980+
WHERE uvdv.dashboard_id = $1 AND b.graffiti_text = $2;
981+
`, dashboardId, graffiti)
982+
})
983+
984+
// fetch validators that are not yet in the dashboard and associated with the graffiti, up to the limit
1036985
var validatorIndicesToInsert []uint64
1037-
for _, validatorIndex := range validatorIndicesToAdd {
1038-
if _, ok := existingValidatorsMap[validatorIndex]; ok {
1039-
validatorIndicesToUpdate = append(validatorIndicesToUpdate, validatorIndex)
1040-
} else {
1041-
validatorIndicesToInsert = append(validatorIndicesToInsert, validatorIndex)
1042-
}
1043-
}
986+
g.Go(func() error {
987+
return d.readerDb.SelectContext(gCtx, &validatorIndicesToInsert, `
988+
SELECT DISTINCT b.proposer
989+
FROM blocks b
990+
LEFT JOIN users_val_dashboards_validators uvdv ON b.proposer = uvdv.validator_index AND uvdv.dashboard_id = $1
991+
WHERE b.graffiti_text = $2 AND uvdv.validator_index IS NULL
992+
ORDER BY b.proposer
993+
LIMIT $3;
994+
`, dashboardId, graffiti, limit)
995+
})
1044996

1045-
// update the group for all existing validators
1046-
validatorIndices := make([]uint64, 0, int(limit))
1047-
validatorIndices = append(validatorIndices, validatorIndicesToUpdate...)
1048-
1049-
// insert the new validators up to the allowed user max limit taking into account how many validators are already in the dashboard
1050-
if len(validatorIndicesToInsert) > 0 {
1051-
freeSpace := int(limit) - len(existingValidators)
1052-
if freeSpace > 0 {
1053-
if len(validatorIndicesToInsert) > freeSpace { // cap inserts to the amount of free space available
1054-
log.Infof("limiting the number of validators to insert to %d", freeSpace)
1055-
validatorIndicesToInsert = validatorIndicesToInsert[:freeSpace]
1056-
}
1057-
validatorIndices = append(validatorIndices, validatorIndicesToInsert...)
1058-
}
997+
err := g.Wait()
998+
if err != nil {
999+
return nil, err
10591000
}
10601001

1061-
if len(validatorIndices) == 0 {
1062-
// no validators to add
1063-
return []t.VDBPostValidatorsData{}, nil
1064-
}
1065-
log.Infof("inserting %d new validators and updating %d validators of dashboard %d, limit is %d", len(validatorIndicesToInsert), len(validatorIndicesToUpdate), dashboardId, limit)
1002+
validatorIndices := slices.Concat(validatorIndicesToUpdate, validatorIndicesToInsert)
1003+
10661004
return d.AddValidatorDashboardValidators(ctx, dashboardId, groupId, validatorIndices)
10671005
}
10681006

10691007
func (d *DataAccessService) RemoveValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBIdPrimary, validators []t.VDBValidator) error {
10701008
if len(validators) == 0 {
1071-
// // Remove all validators for the dashboard
1072-
// _, err := d.alloyWriter.ExecContext(ctx, `
1073-
// DELETE FROM users_val_dashboards_validators
1074-
// WHERE dashboard_id = $1
1075-
// `, dashboardId)
1076-
return fmt.Errorf("calling RemoveValidatorDashboardValidators with empty validators list is not allowed")
1009+
// Remove all validators for the dashboard
1010+
// This is usually forbidden by API validation
1011+
_, err := d.alloyWriter.ExecContext(ctx, `
1012+
DELETE FROM users_val_dashboards_validators
1013+
WHERE dashboard_id = $1
1014+
`, dashboardId)
1015+
return err
10771016
}
10781017

10791018
//Create the query to delete validators

0 commit comments

Comments
 (0)