ObolNetwork / ObolNetwork/charon
app: distribute cluster proposer config (fee recipient / gas limit) to validator clients
@KaloyanTanev is already working on this.
Since Sep 9, 2026.
- Dominant language
- Go
- Stars
- 222
- Forks
- 138
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 35
Description
🎯 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_configJSON:--proposer-settings-file/--validators-proposer-config/--proposerSettingsFile. - Nimbus — per-validator files
<validators-dir>/<pubkey>/suggested_fee_recipient.hex+suggested_gas_limit.json. - Lighthouse —
validator_definitions.yml(keystore-entangled; standalone fee-recipient file removed); CDVN profile authors it fromproposer-config.jsonon every container start (ObolNetwork/charon-distributed-validator-node#487).
- Prysm / Teku / Lodestar — one central
- Applying updates (matters for coordinated
gas_limitbumps, 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 regeneratesvalidator_definitions.ymlfromproposer-config.jsonon 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 #4700). - Resolve canonical config from the lock: keyed by this node's pubshares (the identities the VC manages);
fee_recipientfromFeeRecipientAddresses,gas_limitfromTargetGasLimitwith registration/30M fallback (#4700) -
keymanager pathruled out permanently — file-based is the mechanism - file path: generate
proposer-config.jsonnext to the lock, create-if-missing, best-effort (#4700) - Adapters: CDVN
run.shlayer (all 5 VCs) — Prysm/Teku direct flag (native parse); Lodestar flag + extension shim (cpto.yml: itsreadFilethrowsUnsupportedFileFormatfor.json, but content parses — unknownbuilder.enabledignored,gas_limitmust be string; verified inpackages/cli/src/util/proposerConfig.ts); Nimbus renderer → per-validatorsuggested_fee_recipient.hex+suggested_gas_limit.json; Lighthouse (new CDVN profile) renderer → fullvalidator_definitions.ymlgeneration (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 (ObolNetwork/charon-distributed-validator-node#486, #487)
-
charonedit-in-sync flow: extendcharon alpha feerecipient fetch(or siblingvc-config sync) to rewriteproposer-config.jsonfrom the effective quorum-gated state — the existingsign→ Obol-API-quorum →fetchflow is the sync point;signshould print the follow-through (fetch on every node + VC restart on Prysm/Lodestar/Lighthouse). Impl notes: export/move the unexported generator helpers fromappforcmd/use; rewrite via temp-file+rename (atomic vs Teku's periodic re-read); #4693's mismatch metric detects laggards
🔗 References
- Epic: #4324
- Proposer preferences duty: #4691
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.