Skip to content

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

Open

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 (each gets a CDVN wrapper adapter rendering its native format):
    • Prysm / Teku / Lodestar — one central proposer_config JSON/YAML: --proposer-settings-file / --validators-proposer-config / --proposerSettingsFile. Rendered from the canonical file at container start (Teku/Prysm images gain a static jq, Lodestar renders via node).
    • Nimbus — per-validator files <validators-dir>/<pubkey>/suggested_fee_recipient.hex + suggested_gas_limit.json, rendered per keystore.
    • Lighthouse — validator_definitions.yml (keystore-entangled; standalone fee-recipient file removed); CDVN profile authors it from the canonical file 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.

Canonical charon schema (decision 2026-09-18, supersedes emitting Prysm's native format): the file is charon's own version: 1 schema — the Prysm/Teku shape minus all pre-gloas legacy fields (no builder.enabled, one gas_limit at the option level). It is sparse: default_config carries the per-field majority fee_recipient/gas_limit (plus the gloas builder block — min_bid, builder_boost_factor, max_execution_payment, builders[{url}] — when --builder-urls is set), and proposer_config lists only diverging validators, carrying only the diverging fields. Uniform contract: any absent field falls back one level up (entries → default_config, builders[] entries → the enclosing builder block); booleans are never optional (jq's // swallows false); all values are strings. A uniform cluster's file is ~330 bytes regardless of validator count. builder.enabled semantics moved out of the file entirely — wrappers source it from the BUILDER_API_ENABLED env var. All five VCs live-verified against the schema (charon + VC container + public hoodi BN).

Tasks

🔗 References

Metadata

Metadata

Assignees

Labels

protocolProtocol Team tickets

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions