Skip to content

feat(beacon): add Platåberget as a built-in network - #635

Open
MegaRedHand wants to merge 2 commits into
feat/beacon-gloasfrom
feat/beacon-plataberget-devnet
Open

MegaRedHand wants to merge 2 commits into
feat/beacon-gloasfrom
feat/beacon-plataberget-devnet

Conversation

@MegaRedHand

Copy link
Copy Markdown
Collaborator

🗒️ Description / Motivation

Platåberget is the long-lived Glamsterdam testnet (glamsterdam-devnet-8). It is the one public chain already past gloas: it started at fulu and forked to gloas at epoch 1536 (around 2026-08-20). That makes it the live target for the gloas follower this PR stacks on (#634).

This PR adds --network plataberget as a built-in network, next to mainnet, Sepolia and Hoodi.

What Changed

Embedded chain (bin/ethlambda/src/network/built_in.rs, bin/ethlambda/assets/plataberget/)

  • BuiltInNetwork::Plataberget, with config.yaml and bootstrap_nodes.yaml copied byte for byte from ethpandaops/glamsterdam-devnets' network-configs/devnet-8/metadata. There is no eth-clients repo for it.
  • Genesis values: genesis_time 1786622400 (2026-08-13 12:00 UTC), genesis_validators_root 0xbb4a1a9e…. Both come from the published genesis.ssz, match genesis_validators_root.txt, and match what checkpoint-sync.plataberget.ethpandaops.io/eth/v1/beacon/genesis returns.

CONFIG_NAME comes from --network (BuiltInNetwork::resolve)

  • Platåberget's file says CONFIG_NAME: 'testnet', a placeholder ethpandaops keeps for Prysm. Without an override, --network plataberget would log and report testnet.
  • resolve now sets every built-in's CONFIG_NAME to its --network name. The eth-clients files already carry their own names, so nothing changes for mainnet, Sepolia or Hoodi.

Offline pins (tests in built_in.rs)

  • The fork versions, GLOAS_FORK_EPOCH and deposit chain id are read from the file, not defaulted to mainnet's.
  • The genesis root and both fork versions are pinned against digests in the chain's own bootnode ENRs:
    • 5c943807: fulu at genesis, from the ENRs signed before the fork.
    • 98c910cf: gloas, from the ENRs signed after it.

Docs: every list of built-in networks (README.md, docs/cli.md, docs/beacon_wire.md, docs/checkpoint_sync.md, CLAUDE.md, the --network help). "Every built-in network has been live for years" became "is a live chain", since this one launched in August.

Limitations

The follower can't follow Platåberget yet. Checkpoint sync succeeds: the anchor decodes as gloas and passes the genesis checks against the new constants. Then refuse_unfollowable_fork refuses it, since the chain actor does not take gloas blocks yet. That changes once the follower takes gloas blocks and anchors.

How to Test

cargo test -p ethlambda --profile release-fast --bins network::
ethlambda beacon --network plataberget --checkpoint-sync-url https://checkpoint-sync.plataberget.ethpandaops.io

The second command resolves the network (the digest at the current epoch is gloas's 98c910cf). It downloads and verifies the finalized anchor, then refuses it:

INFO ethlambda: Beacon checkpoint sync complete slot=347840 fork=gloas validators=85453 finalized_epoch=10868 anchor_block_slot=347840
ERROR ethlambda: Cannot anchor at the checkpoint provider's state err=this build cannot follow gloas yet; the anchor is at gloas and nothing past this point will import until that support lands

Platåberget is the long-lived Glamsterdam testnet (glamsterdam-devnet-8),
meant to run for months as the place to test post-Glamsterdam Ethereum
before Sepolia and Hoodi fork. It is the one public chain already past
gloas, so building it in gives the gloas follower a live target: once
the follower takes gloas blocks, `--network plataberget` follows it
without fetching a network directory.

It has no eth-clients repo, so its config.yaml and bootstrap_nodes.yaml
come byte for byte from ethpandaops/glamsterdam-devnets'
network-configs/devnet-8. The genesis values are read off that
directory's genesis.ssz and match what its checkpoint provider serves.
The root and both fork versions are pinned offline by the digests its
bootnode ENRs publish, from both sides of the gloas fork.

The file says `CONFIG_NAME: 'testnet'`, a placeholder ethpandaops keeps
for Prysm, so a built-in network now takes its CONFIG_NAME from its
`--network` name. The eth-clients files already carry their own names,
so nothing changes for mainnet, Sepolia or Hoodi.

Checkpoint sync against Platåberget downloads a gloas anchor and verifies
it against these genesis values, then stops at `refuse_unfollowable_fork`,
since the follower does not take gloas blocks yet.
@github-actions

Copy link
Copy Markdown

🤖 Kimi Code Review

I'll review this PR which adds Platåberget (Glamsterdam testnet) as a built-in network. This is primarily a configuration/network addition with minimal code changes.

Summary

The PR adds a new built-in network "plataberget" (Glamsterdam devnet-8) to the ethlambda client. The changes are mostly mechanical: adding config files, updating enum variants, and adjusting documentation. The code changes are in built_in.rs with some supporting updates.


Detailed Review

1. bin/ethlambda/assets/plataberget/bootstrap_nodes.yaml

Line 20: Missing newline at end of file. While minor, this can cause issues with some tools and creates unnecessary diff noise on future edits.

# Current last line has no trailing newline:
+- enr:-QEYuEBpJHYFTuleF94yNA-eJXNOq4ZQuuGb4vs7uMfGxKXFjQDk5-hyqpKGDh2GSh7rYMHYdml8QPq1jj1_KDSH65btAYdhdHRuZXRziAAAAAAAAAAAg2NnY4GAhmNsaWVudM-IR3JhbmRpbmWFMi4wLjWEZXRoMpBclDgHgHMxgwAGAAAAAAAAgmlkgnY0gmlwhI_GaY2DaXA2kCYEqIAABAHQAAAAA0hsQAuDbmZkhJjJEM-EcXVpY4IjKYVxdWljNoIjW4lzZWNwMjU2azGhA7mE6wvAxEMRBo5iIclDEOMWhCudz8dSYx6N4pxKrXroiHN5bmNuZXRzAIN0Y3CCIyiEdGNwNoIjWoN1ZHCCIyiEdWRwNoIjWg

Recommendation: Add trailing newline.


2. bin/ethlambda/assets/plataberget/config.yaml

Lines 15-16: CONFIG_NAME: 'testnet' with comment explaining it's a Prysm workaround. This is correctly overridden in code later.

Line 19: TERMINAL_TOTAL_DIFFICULTY: 0 — This is a post-Merge network (all forks from genesis), so TTD of 0 is correct.

Lines 31-32: MIN_GENESIS_TIME: 1786622400 corresponds to 2026-Aug-13 12:00:00 PM UTC per comment. Verify this matches the actual genesis time constant in built_in.rs (line 179: 1_786_622_400). ✅ Confirmed match.

Line 33: GENESIS_FORK_VERSION: 0x10733183 — Verified in test at built_in.rs:262 as [0x10, 0x73, 0x31, 0x83]. ✅

Lines 52-53: GLOAS_FORK_EPOCH: 1536 — The PR comment notes this is already past, and the follower doesn't support gloas yet. This is a known limitation correctly documented.

Line 55: HEZE_FORK_VERSION: 0x90000000 with HEZE_FORK_EPOCH: 18446744073709551615 (FAR_FUTURE_EPOCH). ✅ Standard disabled fork pattern.

Lines 149-150: DEPOSIT_CHAIN_ID: 7091047534 / DEPOSIT_NETWORK_ID: 7091047534 — Matches test assertion at built_in.rs:266 (7_091_047_534). ✅

Lines 236-237: BLOB_SCHEDULE with EPOCH: 0, MAX_BLOBS_PER_BLOCK: 21. Note this differs from MAX_BLOBS_PER_BLOCK: 6 (Deneb default) and MAX_BLOBS_PER_BLOCK_ELECTRA: 9. The blob schedule override at genesis is intentional for this devnet.

Potential issue: MAX_BLOBS_PER_BLOCK at line 175 is 6, but blob schedule says 21 at epoch 0. This is inconsistent — if the schedule applies, the base value is shadowed. However, this appears to be how the devnet is configured; the code should use get_max_blobs_per_block(epoch) which checks the schedule. Worth confirming the client correctly prioritizes BLOB_SCHEDULE.


3. bin/ethlambda/src/network/built_in.rs

Lines 32, 38: BuiltInNetwork::Plataberget added to ALL array. Array size updated from 3 to 4. ✅

Lines 79-86: CONFIG_NAME override logic:

config.config_name = self
    .name()
    .try_into()
    .expect("a built-in network's name fits a ConfigName");

This is correct and addresses the Prysm placeholder issue. The expect is safe because all built-in names are ASCII lowercase and fit the ConfigName constraints.

However, consider: This override applies to all built-in networks, not just Plataberget. The comment says "for them this changes nothing" since their files already match. This is true for mainnet/sepolia/hoodi, but it's worth noting this is a subtle behavioral change if any future built-in network has a mismatched CONFIG_NAME.

Lines 167-180: PLATABERGET constant definition:

const PLATABERGET: EmbeddedChain = EmbeddedChain {
    config_yaml: include_str!("../../assets/plataberget/config.yaml"),
    bootnodes_yaml: include_str!("../../assets/plataberget/bootstrap_nodes.yaml"),
    genesis_time: 1_786_622_400,
    genesis_validators_root: "bb4a1a9e3f7f4e10edcd734e4acc3b5ffd4f830efe0af2748fa458cfee5d2658",
};

Critical verification needed: The genesis_validators_root hex string should be exactly 64 characters (32 bytes). Count: bb4a1a9e3f7f4e10edcd734e4acc3b5ffd4f830efe0af2748fa458cfee5d2658 = 64 chars. ✅

The comment says this agrees with genesis_validators_root.txt from the source repo. Good provenance documentation.

Lines 257-267: Test assertions for Plataberget config:

let plataberget = BuiltInNetwork::Plataberget.resolve().unwrap();
assert_eq!(plataberget.genesis_fork_version, [0x10, 0x73, 0x31, 0x83]);
assert_eq!(plataberget.fork_at_epoch(0), ForkName::Fulu);
assert_eq!(plataberget.fulu_fork_version, [0x70, 0x73, 0x31, 0x83]);
assert_eq!(plataberget.gloas_fork_version, [0x80, 0x73, 0x31, 0x83]);
assert_eq!(plataberget.gloas_fork_epoch, 1_536);
assert_eq!(plataberget.deposit_chain_id, 7_091_047_534);

✅ All values match config file. The fork_at_epoch(0) == ForkName::Fulu assertion correctly captures that genesis is at Fulu (all prior forks at epoch 0, but Fulu is the latest active one at genesis).

Lines 269-277: CONFIG_NAME override test:

#[test]
fn plataberget_is_named_for_its_flag_rather_than_its_file() {
    let parsed = ConfigFile::parse(PLATABERGET.config_yaml).unwrap();
    assert_eq!(parsed.config.config_name.as_str(), "testnet");

    let resolved = BuiltInNetwork::Plataberget.resolve().unwrap();
    assert_eq!(resolved.config.config_name.as_str(), "plataberget");
}

✅ Good test covering the override behavior.

Lines 310-324: Fork digest tests with detailed comments explaining bootnode ENR provenance. The dual assertions (epoch 0 and gloas fork epoch) verify both sides of the fork boundary. ✅


4. Documentation Updates

All documentation (CLAUDE.md, README.md, docs/*.md) is consistently updated. No issues.

One minor point in CLAUDE.md line 458: "Platåberget (the Glamsterdam testnet) is already past its GLOAS_FORK_EPOCH, so its checkpoint anchor is a gloas state that refuse_unfollowable_fork refuses"

This is important context — the network is added but cannot actually be followed yet because the client rejects gloas anchors. The PR is preparatory.


5. General Observations

No consensus-critical code changes: This PR touches no attestation processing, fork choice, state transition, or signature verification logic. It's purely network configuration plumbing.

Security consideration: The bootstrap nodes are unverified in the PR. As with other built-in networks, these are trusted bootstrap points. The ENRs contain signatures that will be validated by discv5; the client isn't blindly trusting them for consensus.

Size impact: The added files are ~18KB config + ~4KB bootnodes = ~22KB. The include_str! macros embed these in the binary. Negligible size increase.


Issues Found

Severity Location Issue Recommendation
Minor bootstrap_nodes.yaml:20 Missing trailing newline Add newline
Question config.yaml:175,236 MAX_BLOBS_PER_BLOCK: 6 vs BLOB_SCHEDULE epoch 0: 21 Verify get_max_blobs_per_block correctly uses schedule

Verdict

Approve with minor suggestion. The PR is well-structured, thoroughly tested, and correctly documented. The CONFIG_NAME override is handled cleanly. The only actionable item is adding a trailing newline to bootstrap_nodes.yaml.


Automated review by Kimi (Moonshot AI) · kimi-k2.6 · custom prompt

@github-actions

Copy link
Copy Markdown

🤖 Claude Code Review

Review of PR 635: add Platåberget as a built-in network

Overall this looks good. I did not build or run the tests. Nothing I found blocks merging.

Checked and fine

  • The new BuiltInNetwork::Plataberget variant is wired through ALL (now [Self; 4]), name(), chain() and the --network name parsing. The NetworkSpec::parse test covers the new name.
  • config_name is overridden after the file is parsed. A --network plataberget node reports that name, not the file's testnet placeholder. The override only matters for this network, because the eth-clients files already carry their own names. config_name is a label with no consensus effect. A resume only warns when it changes (main.rs:1806), so the override can't brick an existing data directory. The .expect on try_into is safe for short constant names.
  • The genesis constants are pinned offline by real fork digests taken from the bootnode ENRs. The test checks epoch 0 (fulu, which includes the blob schedule) and the gloas epoch, so it covers both fork versions and the root. This is the same approach already used for Sepolia and Hoodi.
  • The config carries PRESET_BASE: mainnet, so it passes check_preset. The scheduled gloas fork triggers the existing unfollowed-fork warning, and refuse_unfollowable_fork still stops the checkpoint-synced anchor, as the commit message says.

Nits, none blocking

  • bin/ethlambda/src/network/built_in.rs:85-91: the comment names Platåberget's testnet placeholder inside generic resolve code. That is fine today. If a fifth network ever has a mismatched name, the comment should be generalised.
  • The CLAUDE.md working-tree change (git diff) removes every Platåberget mention from the docs. The committed README.md, docs/cli.md, docs/beacon_wire.md and docs/checkpoint_sync.md still mention it. If that edit is unintentional, revert it. If it isn't, the docs and code disagree.
  • cli.rs:89 and cli.rs:268 hard-code the list of built-in names in doc comments, in addition to ALL and the tests. These can drift the next time a network is added. You could derive the list from BuiltInNetwork::ALL in the error text and leave the doc comments generic.
  • The genesis_time comment (2026-08-13 12:00:00 UTC) matches 1_786_622_400.

Automated review by Claude (Anthropic) · sonnet · custom prompt

@github-actions

Copy link
Copy Markdown

🤖 Codex Code Review

Looks good overall. I don’t see a consensus-critical regression in the Rust changes here; the PR is mostly adding a new built-in network and explicitly preserving existing “do not anchor built-ins at genesis” behavior.

  • bin/ethlambda/src/network/built_in.rs:87

    • Overriding config.config_name to the CLI-facing built-in name is reasonable and appears safe: config_name is treated as non-consensus metadata elsewhere (bin/ethlambda/src/main.rs:1503, bin/ethlambda/src/main.rs:1806), so this should not affect fork choice, attestation validation, state transition, or SSZ behavior.
    • Nice that this is pinned by tests in bin/ethlambda/src/network/built_in.rs:273.
  • bin/ethlambda/src/network/built_in.rs:176

    • The Plataberget constants are only as trustworthy as the embedded genesis_time / genesis_validators_root. The added offline digest checks in bin/ethlambda/src/network/built_in.rs:290 are a good safeguard, and they reduce the chance of silently shipping the wrong root or fork version.
  • bin/ethlambda/src/main.rs:1873

    • Good: built-in networks still hard-fail without --checkpoint-sync-url, so this PR does not accidentally allow a beacon follower to “follow” a live chain from slot 0.
    • Also good: the unfollowable-fork guard remains in place before store construction (bin/ethlambda/src/main.rs:1714, bin/ethlambda/src/main.rs:1879), which is important for avoiding a misleadingly live node on Gloas.
  • bin/ethlambda/src/network/mod.rs:50

    • Minor maintainability nit only: the unknown-network error list now grows with BuiltInNetwork::ALL, which is fine, but adding more built-ins will make the message longer. Not a bug.
  • bin/ethlambda/src/network/built_in.rs:224

    • The bootnode-parsing test only checks syntactic ENR parsing, not that the ENRs’ fork digests are consistent with the embedded config beyond the selected pinned examples. That’s acceptable, but if you want stronger defense-in-depth later, you could validate all embedded ENRs’ eth2 entries against the configured fork schedule where applicable.

No security, memory-safety, or performance issues jump out from the code touched in this PR. No fork-choice / attestation / justification-finalization / SSZ correctness concerns are introduced by these changes.


Automated review by OpenAI Codex · gpt-5.4 · custom prompt

@MegaRedHand MegaRedHand mentioned this pull request Oct 1, 2026
2 of 3 tasks
@MegaRedHand MegaRedHand added the beacon Ethereum Beacon Chain client label Oct 1, 2026

This branch has not been deployed

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

Labels

beacon Ethereum Beacon Chain client

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant