Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(BEDS-500) don't throw error when adding more validators than limit allows #877

Merged
merged 1 commit into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions backend/pkg/api/data_access/dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,6 @@ func (d *DummyService) GetValidatorDashboardGroupExists(ctx context.Context, das
return true, nil
}

func (d *DummyService) GetValidatorDashboardExistingValidatorCount(ctx context.Context, dashboardId t.VDBIdPrimary, validators []t.VDBValidator) (uint64, error) {
return getDummyData[uint64]()
}

func (d *DummyService) AddValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, validators []t.VDBValidator) ([]t.VDBPostValidatorsData, error) {
return getDummyData[[]t.VDBPostValidatorsData]()
}
Expand Down
1 change: 0 additions & 1 deletion backend/pkg/api/data_access/vdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ type ValidatorDashboardRepository interface {
GetValidatorDashboardGroupCount(ctx context.Context, dashboardId t.VDBIdPrimary) (uint64, error)
GetValidatorDashboardGroupExists(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64) (bool, error)

GetValidatorDashboardExistingValidatorCount(ctx context.Context, dashboardId t.VDBIdPrimary, validators []t.VDBValidator) (uint64, error)
AddValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, validators []t.VDBValidator) ([]t.VDBPostValidatorsData, error)
AddValidatorDashboardValidatorsByDepositAddress(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, address string, limit uint64) ([]t.VDBPostValidatorsData, error)
AddValidatorDashboardValidatorsByWithdrawalAddress(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, address string, limit uint64) ([]t.VDBPostValidatorsData, error)
Expand Down
257 changes: 98 additions & 159 deletions backend/pkg/api/data_access/vdb_management.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/hex"
"fmt"
"math/big"
"slices"
"sort"
"strconv"
"strings"
Expand All @@ -15,7 +16,6 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/gobitfly/beaconchain/pkg/api/enums"
t "github.com/gobitfly/beaconchain/pkg/api/types"
"github.com/gobitfly/beaconchain/pkg/commons/log"
"github.com/gobitfly/beaconchain/pkg/commons/utils"
constypes "github.com/gobitfly/beaconchain/pkg/consapi/types"
"github.com/lib/pq"
Expand Down Expand Up @@ -790,21 +790,6 @@ func (d *DataAccessService) GetValidatorDashboardGroupExists(ctx context.Context
return groupExists, err
}

// return how many of the passed validators are already in the dashboard
func (d *DataAccessService) GetValidatorDashboardExistingValidatorCount(ctx context.Context, dashboardId t.VDBIdPrimary, validators []t.VDBValidator) (uint64, error) {
if len(validators) == 0 {
return 0, nil
}

var count uint64
err := d.alloyReader.GetContext(ctx, &count, `
SELECT COUNT(*)
FROM users_val_dashboards_validators
WHERE dashboard_id = $1 AND validator_index = ANY($2)
`, dashboardId, pq.Array(validators))
return count, err
}

func (d *DataAccessService) AddValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, validators []t.VDBValidator) ([]t.VDBPostValidatorsData, error) {
if len(validators) == 0 {
// No validators to add
Expand Down Expand Up @@ -889,191 +874,145 @@ func (d *DataAccessService) AddValidatorDashboardValidators(ctx context.Context,
return result, nil
}

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

if len(addressParsed) != 20 {
return nil, fmt.Errorf("invalid deposit address: %s", address)
}
var validatorIndicesToAdd []uint64
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)
if err != nil {
return nil, err
}
g, gCtx := errgroup.WithContext(ctx)

// retrieve the existing validators
var existingValidators []uint64
err = d.alloyWriter.SelectContext(ctx, &existingValidators, "SELECT validator_index FROM users_val_dashboards_validators WHERE dashboard_id = $1", dashboardId)
if err != nil {
return nil, err
}
existingValidatorsMap := make(map[uint64]bool, len(existingValidators))
for _, validatorIndex := range existingValidators {
existingValidatorsMap[validatorIndex] = true
}

// filter out the validators that are already in the dashboard
// fetch validators that are already in the dashboard and associated with the deposit address
var validatorIndicesToUpdate []uint64

g.Go(func() error {
return d.readerDb.SelectContext(gCtx, &validatorIndicesToUpdate, `
SELECT DISTINCT uvdv.validator_index
FROM validators v
JOIN eth1_deposits d ON v.pubkey = d.publickey
JOIN users_val_dashboards_validators uvdv ON v.validatorindex = uvdv.validator_index
WHERE uvdv.dashboard_id = $1 AND d.from_address = $2;
`, dashboardId, addressParsed)
})

// fetch validators that are not yet in the dashboard and associated with the deposit address, up to the limit
var validatorIndicesToInsert []uint64
for _, validatorIndex := range validatorIndicesToAdd {
if _, ok := existingValidatorsMap[validatorIndex]; ok {
validatorIndicesToUpdate = append(validatorIndicesToUpdate, validatorIndex)
} else {
validatorIndicesToInsert = append(validatorIndicesToInsert, validatorIndex)
}
}
g.Go(func() error {
return d.readerDb.SelectContext(gCtx, &validatorIndicesToInsert, `
SELECT DISTINCT v.validatorindex
FROM validators v
JOIN eth1_deposits d ON v.pubkey = d.publickey
LEFT JOIN users_val_dashboards_validators uvdv ON v.validatorindex = uvdv.validator_index AND uvdv.dashboard_id = $1
WHERE d.from_address = $2 AND uvdv.validator_index IS NULL
ORDER BY v.validatorindex
LIMIT $3;
`, dashboardId, addressParsed, limit)
})

// update the group for all existing validators
validatorIndices := make([]uint64, 0, int(limit))
validatorIndices = append(validatorIndices, validatorIndicesToUpdate...)

// insert the new validators up to the allowed user max limit taking into account how many validators are already in the dashboard
if len(validatorIndicesToInsert) > 0 {
freeSpace := int(limit) - len(existingValidators)
if freeSpace > 0 {
if len(validatorIndicesToInsert) > freeSpace { // cap inserts to the amount of free space available
log.Infof("limiting the number of validators to insert to %d", freeSpace)
validatorIndicesToInsert = validatorIndicesToInsert[:freeSpace]
}
validatorIndices = append(validatorIndices, validatorIndicesToInsert...)
}
err = g.Wait()
if err != nil {
return nil, err
}

if len(validatorIndices) == 0 {
// no validators to add
return []t.VDBPostValidatorsData{}, nil
}
log.Infof("inserting %d new validators and updating %d validators of dashboard %d, limit is %d", len(validatorIndicesToInsert), len(validatorIndicesToUpdate), dashboardId, limit)
validatorIndices := slices.Concat(validatorIndicesToUpdate, validatorIndicesToInsert)

return d.AddValidatorDashboardValidators(ctx, dashboardId, groupId, validatorIndices)
}

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

// retrieve the existing validators
var existingValidators []uint64
err = d.alloyWriter.SelectContext(ctx, &existingValidators, "SELECT validator_index FROM users_val_dashboards_validators WHERE dashboard_id = $1", dashboardId)
if err != nil {
return nil, err
}
existingValidatorsMap := make(map[uint64]bool, len(existingValidators))
for _, validatorIndex := range existingValidators {
existingValidatorsMap[validatorIndex] = true
}
g, gCtx := errgroup.WithContext(ctx)

// filter out the validators that are already in the dashboard
// fetch validators that are already in the dashboard and associated with the withdrawal address
var validatorIndicesToUpdate []uint64
g.Go(func() error {
return d.readerDb.SelectContext(gCtx, &validatorIndicesToUpdate, `
SELECT DISTINCT uvdv.validator_index
FROM validators v
JOIN users_val_dashboards_validators uvdv ON v.validatorindex = uvdv.validator_index
WHERE uvdv.dashboard_id = $1 AND v.withdrawalcredentials = $2 AND uvdv.dashboard_id = $2;
`, dashboardId, addressParsed)
})

// fetch validators that are not yet in the dashboard and associated with the withdrawal address, up to the limit
var validatorIndicesToInsert []uint64
for _, validatorIndex := range validatorIndicesToAdd {
if _, ok := existingValidatorsMap[validatorIndex]; ok {
validatorIndicesToUpdate = append(validatorIndicesToUpdate, validatorIndex)
} else {
validatorIndicesToInsert = append(validatorIndicesToInsert, validatorIndex)
}
}
g.Go(func() error {
return d.readerDb.SelectContext(gCtx, &validatorIndicesToInsert, `
SELECT DISTINCT v.validatorindex
FROM validators v
LEFT JOIN users_val_dashboards_validators uvdv ON v.validatorindex = uvdv.validator_index AND uvdv.dashboard_id = $1
WHERE v.withdrawalcredentials = $2 AND uvdv.validator_index IS NULL
ORDER BY v.validatorindex
LIMIT $3;
`, dashboardId, addressParsed, limit)
})

// update the group for all existing validators
validatorIndices := make([]uint64, 0, int(limit))
validatorIndices = append(validatorIndices, validatorIndicesToUpdate...)

// insert the new validators up to the allowed user max limit taking into account how many validators are already in the dashboard
if len(validatorIndicesToInsert) > 0 {
freeSpace := int(limit) - len(existingValidators)
if freeSpace > 0 {
if len(validatorIndicesToInsert) > freeSpace { // cap inserts to the amount of free space available
log.Infof("limiting the number of validators to insert to %d", freeSpace)
validatorIndicesToInsert = validatorIndicesToInsert[:freeSpace]
}
validatorIndices = append(validatorIndices, validatorIndicesToInsert...)
}
err = g.Wait()
if err != nil {
return nil, err
}

if len(validatorIndices) == 0 {
// no validators to add
return []t.VDBPostValidatorsData{}, nil
}
log.Infof("inserting %d new validators and updating %d validators of dashboard %d, limit is %d", len(validatorIndicesToInsert), len(validatorIndicesToUpdate), dashboardId, limit)
validatorIndices := slices.Concat(validatorIndicesToUpdate, validatorIndicesToInsert)

return d.AddValidatorDashboardValidators(ctx, dashboardId, groupId, validatorIndices)
}

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

// retrieve the existing validators
var existingValidators []uint64
err = d.alloyWriter.SelectContext(ctx, &existingValidators, "SELECT validator_index FROM users_val_dashboards_validators WHERE dashboard_id = $1", dashboardId)
if err != nil {
return nil, err
}
existingValidatorsMap := make(map[uint64]bool, len(existingValidators))
for _, validatorIndex := range existingValidators {
existingValidatorsMap[validatorIndex] = true
}

// filter out the validators that are already in the dashboard
// fetch validators that are already in the dashboard and associated with the graffiti
var validatorIndicesToUpdate []uint64
g.Go(func() error {
return d.readerDb.SelectContext(gCtx, &validatorIndicesToUpdate, `
SELECT DISTINCT uvdv.validator_index
FROM blocks b
JOIN users_val_dashboards_validators uvdv ON b.proposer = uvdv.validator_index
WHERE uvdv.dashboard_id = $1 AND b.graffiti_text = $2;
`, dashboardId, graffiti)
})

// fetch validators that are not yet in the dashboard and associated with the graffiti, up to the limit
var validatorIndicesToInsert []uint64
for _, validatorIndex := range validatorIndicesToAdd {
if _, ok := existingValidatorsMap[validatorIndex]; ok {
validatorIndicesToUpdate = append(validatorIndicesToUpdate, validatorIndex)
} else {
validatorIndicesToInsert = append(validatorIndicesToInsert, validatorIndex)
}
}
g.Go(func() error {
return d.readerDb.SelectContext(gCtx, &validatorIndicesToInsert, `
SELECT DISTINCT b.proposer
FROM blocks b
LEFT JOIN users_val_dashboards_validators uvdv ON b.proposer = uvdv.validator_index AND uvdv.dashboard_id = $1
WHERE b.graffiti_text = $2 AND uvdv.validator_index IS NULL
ORDER BY b.proposer
LIMIT $3;
`, dashboardId, graffiti, limit)
})

// update the group for all existing validators
validatorIndices := make([]uint64, 0, int(limit))
validatorIndices = append(validatorIndices, validatorIndicesToUpdate...)

// insert the new validators up to the allowed user max limit taking into account how many validators are already in the dashboard
if len(validatorIndicesToInsert) > 0 {
freeSpace := int(limit) - len(existingValidators)
if freeSpace > 0 {
if len(validatorIndicesToInsert) > freeSpace { // cap inserts to the amount of free space available
log.Infof("limiting the number of validators to insert to %d", freeSpace)
validatorIndicesToInsert = validatorIndicesToInsert[:freeSpace]
}
validatorIndices = append(validatorIndices, validatorIndicesToInsert...)
}
err := g.Wait()
if err != nil {
return nil, err
}

if len(validatorIndices) == 0 {
// no validators to add
return []t.VDBPostValidatorsData{}, nil
}
log.Infof("inserting %d new validators and updating %d validators of dashboard %d, limit is %d", len(validatorIndicesToInsert), len(validatorIndicesToUpdate), dashboardId, limit)
validatorIndices := slices.Concat(validatorIndicesToUpdate, validatorIndicesToInsert)

return d.AddValidatorDashboardValidators(ctx, dashboardId, groupId, validatorIndices)
}

func (d *DataAccessService) RemoveValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBIdPrimary, validators []t.VDBValidator) error {
if len(validators) == 0 {
// // Remove all validators for the dashboard
// _, err := d.alloyWriter.ExecContext(ctx, `
// DELETE FROM users_val_dashboards_validators
// WHERE dashboard_id = $1
// `, dashboardId)
return fmt.Errorf("calling RemoveValidatorDashboardValidators with empty validators list is not allowed")
// Remove all validators for the dashboard
// This is usually forbidden by API validation
_, err := d.alloyWriter.ExecContext(ctx, `
DELETE FROM users_val_dashboards_validators
WHERE dashboard_id = $1
`, dashboardId)
return err
}

//Create the query to delete validators
Expand Down
Loading
Loading