Optimize beacon API (REST and gRPC) regarding endpoints needing to iterate over the whole validator set.#16838
Conversation
Previous implementation was returning ALL active validators, contrary to what is stated by the godoc.
…f an `int`. Rationale: Most of callers had to do this cast. Now the cast is directly done in the function, which is simpler for callers.
ReadFromEveryValidator==> AllValidatorsReadOnly(func iterator)This (very slightly) adds some complexity to the implementation of `ValidatorsReadOnlySeq` (which is done only once), but it simplifies the call sites (which are numerous).
…dators (mainnet) heap allocation by using `requestedState.ValidatorsReadOnlySeq` instead of `requestedState.Validators`.
…over validators as an input instead of the full validators slice. It avoids a ~2.3M validators (mainnet) materialization.
…): Avoid full validators list (2.3M on mainnet) materialization.
…e): Avoid the full validators list (~2.3M on mainnet) materialization.
…list materialization.
| bal.ActiveCurrentEpoch, err = math.Add64(bal.ActiveCurrentEpoch, val.EffectiveBalance()) | ||
| if err != nil { | ||
| return err | ||
| return nil, nil, fmt.Errorf("add 64: %w", err) |
There was a problem hiding this comment.
I guess Claude did not understand what the function Add64 does :)
There was a problem hiding this comment.
Haha no it was not claude but me.
I always follow this convention:
https://github.com/uber-go/guide/blob/master/style.md#error-wrapping
But yes in this case in not so clear. Will fix
| bal.ActivePrevEpoch, err = math.Add64(bal.ActivePrevEpoch, val.EffectiveBalance()) | ||
| if err != nil { | ||
| return err | ||
| return nil, nil, fmt.Errorf("add 64: %w", err) |
There was a problem hiding this comment.
Same, perhaps "overflow" or " Add64" would be better.
| require.NoError(t, err) | ||
| _, _, err = InitializePrecomputeValidators(t.Context(), s) | ||
| require.ErrorContains(t, "could not read every validator: addition overflows", err) | ||
| require.ErrorContains(t, "add 64: addition overflows", err) |
There was a problem hiding this comment.
interesting, this one actually added the "overflow" statement.
| Err: errors.Wrap(err, "could not determine exited validator indices"), | ||
| Reason: Internal, | ||
| var ( | ||
| activatedIndices, exitedIndices, slashedIndices, ejectedIndices []primitives.ValidatorIndex |
There was a problem hiding this comment.
An only nit is that these variables are not initialized so if no val is activated for example, this function will return nil when before it would return an initialized empty slice. This is fine as far as I can see in the current code.
| slashingsVector := params.BeaconConfig().EpochsPerSlashingsVector | ||
|
|
||
| for idx, validator := range requestedState.ValidatorsReadOnlySeq() { | ||
| if validator.ActivationEpoch() == requestedEpoch { |
There was a problem hiding this comment.
This fixes the linked issue, but the test TestActivatedValidatorIndices was removed, it would be nice to add a regression unit test that actually tests without a request epoch being zero so that we can assert that the indices were not in the reply (the previous test covered this case but asserted the opposite).
|
|
||
| for i := range b.validatorsMultiValue.Len(b) { | ||
| v, err := b.validatorsMultiValue.At(b, uint64(i)) | ||
| if err != nil { |
There was a problem hiding this comment.
do we not need to log errors for this stuff? or does that just clutter?
There was a problem hiding this comment.
This case is basically impossible, since the only way At returns on error is in case we have an out of bound error.
At is between 0 and Len(b), and we have a lock at the start of the function so no mutation of validatorsMultiValue is possible ==> No out of bound error can happen.
Added a log however in 3245eb5.
What type of PR is this?
Bug fix and optimization
What does this PR do? Why is it needed?
Some of the beacon endpoints currently wastefully materialize the entire validator set (~2.3M validators on mainnet) with the
state.Validatorsfunction before filtering interesting validators. The call tostate.Validatorsis not free at all, especially on mainnet, and, because Ethereum doesn't currently have validator index re-uses, this call will become more and more expensive to do.This PR implements the "yield" iterations for these endpoints, allowing them to filter "on the flight" validator they want to keep and thus preventing wasteful costly heap allocation.
Notable example of gain:
/eth/v1/beacon/states/{state_id}/validators?status=withdrawal_possible.Previously, all the validators where materialized in the node heap (~2.3M on mainnet). With this PR, only the exited slashed validators are materialized (1442 on mainnet).
This pull requests:
Which issues(s) does this PR fix?
/prysm/v1/validators/head/active_set_changesreturns the full active validators set, instead of only those that became activated during the epoch #16842Other notes for review
Important
Please read commit by commit, with commit messages, since they contain useful information to understand the code changes.
Note
Two calls to
ValidatorsReadOnlyremain in hdiff. It may probably be interesting to remove them, but it will be done (if needed) in an other PR.EDIT: (Seen with @potuz on Prysm internal messaging system: Will be done in a next PR.)
Acknowledgements