Skip to content

Remote validator client: use v2 proposer duties endpoint from Fulu - #11164

Open
rolfyone wants to merge 6 commits into
Consensys:masterfrom
rolfyone:10092
Open

Remote validator client: use v2 proposer duties endpoint from Fulu#11164
rolfyone wants to merge 6 commits into
Consensys:masterfrom
rolfyone:10092

Conversation

@rolfyone

@rolfyone rolfyone commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

The /eth/v2/validator/duties/proposer/{epoch} endpoint was already implemented server-side (GetProposerDutiesV2), but the remote validator client was always calling the deprecated v1 endpoint and ignoring the isFuluCompatible flag.

At Fulu the dependent root calculation changes: BlockProposalUtilFulu returns previousTargetRoot for current-epoch duties and currentTargetRoot for next-epoch duties, whereas the pre-Fulu logic does the opposite. BlockDutyScheduler uses the
fork-correct local calculation to check whether fetched duties are still fresh after a head event. If the remote client fetched via v1 (which forces PHASE0 logic server-side), the returned dependent_root would never match the locally computed
value at Fulu, causing duties to appear stale on every head event.

Changes

  • ValidatorApiMethod: add GET_PROPOSER_DUTIES_V2 (eth/v2/validator/duties/proposer/:epoch)
  • GetProposerDutiesV2Request: new request handler for the v2 endpoint (mirrors v1 handler)
  • OkHttpValidatorTypeDefClient: expose getProposerDutiesV2(epoch)
  • RemoteValidatorApiHandler: dispatch to v2 when isFuluCompatible=true (which BlockProductionDutyLoader always passes), fall back to v1 otherwise (for the deprecated beacon API v1 path)
  • Tests covering both isFuluCompatible=false (v1) and isFuluCompatible=true (v2) paths

Note on "required from Gloas"

The beacon-APIs spec says v2 is only required from Gloas in the sense that other clients aren't obligated to implement it before then. The dependent root semantics change at Fulu, so v2 is needed for correctness from Fulu onwards — restricting it
to Gloas would cause incorrect dependent roots at Fulu.

fixes #10092

Documentation

  • I thought about documentation and added the doc-change-required label to this PR if updates are required.

Changelog

  • I thought about adding a changelog entry, and added one if I deemed necessary.

Note

Medium Risk
Changes how proposer duties are fetched during Fulu/Gloas transitions; incorrect routing could affect block proposal scheduling, though fallbacks and tests limit exposure.

Overview
The remote validator client no longer always hits GET /eth/v1/validator/duties/proposer/{epoch}; when isFuluCompatible is true it prefers /eth/v2/..., which returns Fulu-correct dependent_root values so duties are not treated as stale on every head update.

Routing in RemoteValidatorApiHandler: pre-Fulu stays on v1; if v2 returns empty (unsupported BN), it falls back to v1 once and remembers that for later calls; within two epochs of the Gloas fork it uses v2 only (no v1 fallback) and clears the “v2 unsupported” flag so upgraded beacon nodes get a fresh try. Wiring adds GetProposerDutiesV2Request, passes Spec into the handler for the Gloas epoch, plus tests and a changelog note.

Reviewed by Cursor Bugbot for commit a299e26. Bugbot is set up for automated code reviews on this repo. Configure here.

  Summary

  The /eth/v2/validator/duties/proposer/{epoch} endpoint was already implemented server-side (GetProposerDutiesV2), but the remote validator client was always calling the deprecated v1 endpoint and ignoring the isFuluCompatible flag.

  At Fulu the dependent root calculation changes: BlockProposalUtilFulu returns previousTargetRoot for current-epoch duties and currentTargetRoot for next-epoch duties, whereas the pre-Fulu logic does the opposite. BlockDutyScheduler uses the
  fork-correct local calculation to check whether fetched duties are still fresh after a head event. If the remote client fetched via v1 (which forces PHASE0 logic server-side), the returned dependent_root would never match the locally computed
  value at Fulu, causing duties to appear stale on every head event.

  Changes

  - ValidatorApiMethod: add GET_PROPOSER_DUTIES_V2 (eth/v2/validator/duties/proposer/:epoch)
  - GetProposerDutiesV2Request: new request handler for the v2 endpoint (mirrors v1 handler)
  - OkHttpValidatorTypeDefClient: expose getProposerDutiesV2(epoch)
  - RemoteValidatorApiHandler: dispatch to v2 when isFuluCompatible=true (which BlockProductionDutyLoader always passes), fall back to v1 otherwise (for the deprecated beacon API v1 path)
  - Tests covering both isFuluCompatible=false (v1) and isFuluCompatible=true (v2) paths

  Note on "required from Gloas"

  The beacon-APIs spec says v2 is only required from Gloas in the sense that other clients aren't obligated to implement it before then. The dependent root semantics change at Fulu, so v2 is needed for correctness from Fulu onwards — restricting it
  to Gloas would cause incorrect dependent roots at Fulu.

  fixes Consensys#10092
@zilm13

zilm13 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

my only concern is this:


Unconditional v2 with no fallback breaks the VC against any BN without eth/v2/validator/duties/proposer (blocking)

RemoteValidatorApiHandler:222-224 routes to v2 whenever isFuluCompatible, which for the VC is always. Per beacon-APIs #563 v1 is not deprecated (that's planned post-Gloas), so
other clients — and older Teku — legitimately serve only v1 today.

Failure path when the BN returns 404:

  • ResponseHandler.notFoundHandler → Optional.empty() (debug-log only)
  • AbstractDutyLoader.loadDutiesForEpoch → orElseThrow(NodeDataUnavailableException("chain data was not yet available"))

So the VC produces no block proposal duties at all, on every epoch, on every network including pre-Fulu ones, with a misleading "chain data not available" error.


should we force this?
It looks like this should be noted at least as breaking changes from my view, because users could use different VC+BN combinations

@rolfyone

Copy link
Copy Markdown
Contributor Author

Unconditional v2 with no fallback breaks the VC against any BN without eth/v2/validator/duties/proposer (blocking)

this is a functional change, but the reality is we just ignored it for fulu, so i think every other client is using v2 now.

My main concern is gloas issues, so I need to make sure glaos actually works at a minimum before i can really promote this from draft. hoping to look again today.

@zilm13 zilm13 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just few nits


boolean isWithinTwoEpochsOfGloas(final UInt64 epoch) {
return gloasForkEpoch
.filter(g -> g.isGreaterThan(UInt64.ONE))

@zilm13 zilm13 Aug 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?
assuming testnet where we start from 0 epoch, I'd expect to use V2 there inevitably
we have minusMinZero to avoid underflow

final int validatorIndex = 472;
final ProposerDuty schemaValidatorDuties =
new ProposerDuty(blsPublicKey, validatorIndex, UInt64.ZERO);
final ProposerDuty expectedValidatorDuties =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vars schemaValidatorDuties and expectedValidatorDuties are the same

.thenCompose(
result -> {
if (result.isEmpty()) {
LOG.debug(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make it warn. It could happen only once per launch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BEACON API] proposer v2 endpoint

3 participants