Skip to content

app: distribute cluster proposer config (fee recipient / gas limit) to validator clients #4692

Description

@KaloyanTanev

🎯 Problem to be solved

Charon currently ignores the VC's proposer config: prepare_beacon_proposer is a no-op (core/validatorapi/router.go, submitProposalPreparations) and register_validator is ignored (core/validatorapi/validatorapi.go, SubmitValidatorRegistrations). Charon injects the lock's fee recipient itself. As a result the VC-side fee recipient / gas limit are unmanaged and irrelevant today — CDVN VCs set none, and Teku even hardcodes --validators-proposer-default-fee-recipient 0x0000…0000.

Post-gloas the VC signs fee_recipient and target_gas_limit into SignedProposerPreferences (#4691), so its local config becomes load-bearing and must match the lock cluster-wide or the partial signatures won't aggregate. This issue: give Charon a way to push the cluster-canonical fee recipient + gas limit (lock FeeRecipientAddresses / TargetGasLimit) into the VCs.

This has independent, pre-gloas value — it makes VCs hold the correct fee recipient rather than relying on Charon's override — so it can land before the gloas duty.

Sub-task of the gloas epic #4324.

🛠️ Proposed solution

Findings

  • No standard config file exists. The ecosystem is standardizing an API instead — keymanager-APIs#87 (/eth/v1/validator/config, closed/contentious, not ratified), explicitly to kill the "different config dialect per client" burden it names for dappnode/eth-docker.
  • Per-client config surfaces:
    • Prysm / Teku / Lodestar — one central proposer_config JSON: --proposer-settings-file / --validators-proposer-config / --proposerSettingsFile.
    • Nimbus — per-validator files <validators-dir>/<pubkey>/suggested_fee_recipient.hex + suggested_gas_limit.json.
    • Lighthousevalidator_definitions.yml (keystore-entangled; standalone fee-recipient file removed); CDVN profile authors it from proposer-config.json on every container start (Add lighthouse validator client with proposer config charon-distributed-validator-node#487).
  • Applying updates (matters for coordinated gas_limit bumps, which are applied staggered across operators): file rewrite + VC restart everywhere. File hot-reload exists only on Teku (--Xvalidators-proposer-config-refresh-enabled, per epoch) and Nimbus (per-validator files read live); Prysm/Lodestar re-read on restart; the CDVN Lighthouse adapter regenerates validator_definitions.yml from proposer-config.json on every container start, so a restart applies updates there too.

Mechanism

File-based, exclusively. The keymanager API is ruled out permanently (decision 2026-09-17) — charon generates proposer-config.json, per-client adapters derive native formats from it, and updates apply via file rewrite + VC restart. Post-gloas this same file is the alignment surface for the values VCs sign (SignedProposerPreferences, and later the builder url/auth_data signed into BuilderRequestAuth), so it must be byte-consistent across the cluster.

Tasks

  • Decide mechanism: (B) per-client file generation — one JSON parsed natively by Prysm/Teku/Lodestar (JSON is valid YAML); Nimbus + Lighthouse via generate-from-JSON adapters (LH: author the whole validator_definitions.yml — a supported workflow — generate, not merge). Always-on (no feature flag — the diff is small and generation is best-effort/create-once, decided in review of app: generate proposer config file for validator clients #4700).
  • Resolve canonical config from the lock: keyed by this node's pubshares (the identities the VC manages); fee_recipient from FeeRecipientAddresses, gas_limit from TargetGasLimit with registration/30M fallback (app: generate proposer config file for validator clients #4700)
  • keymanager path ruled out permanently — file-based is the mechanism
  • file path: generate proposer-config.json next to the lock, create-if-missing, best-effort (app: generate proposer config file for validator clients #4700)
  • Adapters: CDVN run.sh layer (all 5 VCs) — Prysm/Teku direct flag (native parse); Lodestar flag + extension shim (cp to .yml: its readFile throws UnsupportedFileFormat for .json, but content parses — unknown builder.enabled ignored, gas_limit must be string; verified in packages/cli/src/util/proposerConfig.ts); Nimbus renderer → per-validator suggested_fee_recipient.hex + suggested_gas_limit.json; Lighthouse (new CDVN profile) renderer → full validator_definitions.yml generation (replaces import; --disable-auto-discover). Shared: wait-for-file guard (docker creates a directory when bind-mounting a missing file path), restart-semantics docs (hot: Teku w/ refresh flag, Nimbus; restart: Prysm/Lodestar/Lighthouse)
  • CDVN wiring: mount the config file per VC profile; replace Teku's hardcoded zero fee-recipient default (Wire charon proposer config into validator clients charon-distributed-validator-node#486, cmd: refactor to use create command #487)
  • charon edit-in-sync flow: extend charon alpha feerecipient fetch (or sibling vc-config sync) to rewrite proposer-config.json from the effective quorum-gated state — the existing sign → Obol-API-quorum → fetch flow is the sync point; sign should print the follow-through (fetch on every node + VC restart on Prysm/Lodestar/Lighthouse). Impl notes: export/move the unexported generator helpers from app for cmd/ use; rewrite via temp-file+rename (atomic vs Teku's periodic re-read); core: add gloas proposer preferences duty #4693's mismatch metric detects laggards

🔗 References

Activity

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

Metadata

Metadata

Assignees

Labels

protocolProtocol Team tickets

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions