feat(app): fail fast on lock vs beacon-node fork-schedule mismatch#558
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a startup-time misconfiguration guard to ensure the configured beacon node is on the same network as the cluster lock, failing fast with an operator-facing error when the lock fork version is not present in the beacon node’s fork schedule.
Changes:
- Add an
EthBeaconNodeApiClienthelper to fetch and decode fork-schedulecurrent_versionentries. - Add a startup guard in
appto compare the lock’s fork version against the beacon node’s fork schedule and error on mismatch (with best-effort network naming). - Add beaconmock-backed tests for the matching and mismatching network paths.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| crates/eth2api/src/extensions.rs | Adds a helper to fetch/decode fork-schedule versions for reuse by higher-level guards. |
| crates/app/src/node/mod.rs | Adds the fail-fast fork-schedule verification at startup, a structured error, and tests. |
| crates/app/Cargo.toml | Adds pluto-eth2util dependency needed for fork-version → network naming. |
| Cargo.lock | Records the updated dependency graph. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
f6a2e44 to
20f0492
Compare
| /// schedule (`/eth/v1/config/fork_schedule`), decoded and ordered | ||
| /// oldest-to-newest. The first entry is the genesis fork version, which | ||
| /// identifies the beacon node's network. | ||
| pub async fn fetch_fork_schedule_versions( |
There was a problem hiding this comment.
New API (why not the existing one). This reads the real /eth/v1/config/fork_schedule endpoint (matching Charon's eth2Cl.ForkSchedule()). The sibling fetch_fork_config can't be reused here: it is spec-derived from <FORK>_FORK_VERSION keys over Altair..Fulu only, so it omits the genesis/phase0 entry — the lock's fork version is the genesis fork version, so checking against fetch_fork_config would falsely reject every correct network.
| Err(AppError::ForkScheduleMismatch { | ||
| lock_network: network_name_or_hex(lock_fork_version), | ||
| lock_fork_version: format!("0x{}", hex::encode(lock_fork_version)), | ||
| beacon_node_network: versions | ||
| .first() | ||
| .map(|v| network_name_or_hex(&v[..])) | ||
| .unwrap_or_else(|| "unknown".to_string()), |
There was a problem hiding this comment.
Differs from Charon. Two deliberate deviations from configureEth2Client: (1) an unknown fork version falls back to its 0x… hex here (via network_name_or_hex) instead of Charon's hard "cannot parse" error — more actionable and still fails on genuine mismatch; (2) an empty schedule yields unknown rather than panicking on schedule[0].
Port Charon's `configureEth2Client` guard (app/app.go:1022-1053): after building the eth2 client, fetch the beacon node's fork schedule and error at startup if it does not contain the cluster lock's fork version. This rejects a wrong-network beacon node up front instead of letting the node schedule duties and silently fail signatures later. - eth2api: add `fetch_fork_schedule_versions`, exposing the decoded `current_version`s from `/eth/v1/config/fork_schedule`. - app: `verify_fork_schedule` checks lock fork version membership and, on mismatch, returns `AppError::ForkScheduleMismatch` naming the lock fork version + network and the beacon node network (best-effort, hex fallback for unknown networks). - beaconmock-backed tests for both the matching and mismatched paths.
20f0492 to
40125ee
Compare
- Explain expected endpoint ordering
emlautarom1
left a comment
There was a problem hiding this comment.
Additional wiring check. Minor stuff, LGTM.
Closes #532.
Summary
Fail fast at startup when the beacon node is on a different network than the cluster lock.
Follow-up to #536, which introduced
runand explicitly deferred this misconfiguration guard.Differences from Charon
configureEth2Client, once per independently constructed client (eth2ClandsubmissionEth2Cl). Pluto's two clients target the same endpoint, so one check on the primary client covers both.0x…fork version in the mismatch error — more actionable for an operator, and it still fails on a genuine network mismatch.schedule[0]unconditionally (a panic risk on an empty schedule); Pluto reports the beacon node network asunknownand still returns the structured mismatch error.