Skip to content

Commit afb2528

Browse files
samcmampagent
andauthored
Add Prysm FCR simulator engine (#14)
Amp-Thread-ID: https://ampcode.com/threads/T-01a0183e-812e-71f9-846b-8a6f6fc50c94 Co-authored-by: Amp <amp@ampcode.com>
1 parent 7eabc3f commit afb2528

6 files changed

Lines changed: 444 additions & 6 deletions

File tree

.gitmodules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@
1313
path = engines/lighthouse/lighthouse
1414
url = https://github.com/samcm/lighthouse.git
1515
branch = fcr-simulator
16+
[submodule "engines/prysm/prysm"]
17+
path = engines/prysm/prysm
18+
url = https://github.com/OffchainLabs/prysm.git
19+
branch = fcr

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ All engines live under `engines/<name>/`. Each has a `build.sh` that produces `r
1313
| nimbus | v26.7.0 | Release implementation |
1414
| lodestar | v1.46.0 | Release implementation |
1515
| grandine | PR #656 (`9905f46f`) | Unreleased; Grandine 2.0.6 does not contain FCR |
16+
| prysm | PR #17122 (`5fc7c60d`) | Unreleased; enabled through Prysm's FCR test harness |
1617

17-
Prysm is deferred: PR #15164 implements an older spec (`adiasg/eth2.0-specs:3e3ef28`), not [consensus-specs#4747](https://github.com/ethereum/consensus-specs/pull/4747). Shipping a binary against the older algorithm would contaminate cross-engine comparison. Revisit once the upstream PR rebases.
18+
Prysm support is pinned to the open [PR #17122](https://github.com/OffchainLabs/prysm/pull/17122), not a released client version. The adapter compiles as a test binary so it can reuse Prysm's upstream FCR spectest harness without patching production client code.
1819

1920
## Architecture
2021

@@ -25,9 +26,9 @@ A Go orchestrator owns everything engine-agnostic — ERA file download, checkpo
2526
│ fcr-orchestrator (Go) │
2627
└────────────────────────────────┬────────────────────────┘
2728
│ HTTP (SSZ blocks/states + /fcr-sim/v1/plan)
28-
┌──────────┬──────────┬───┴───────┬──────────┐
29-
fcr-lighthouse fcr-teku fcr-nimbus fcr-lodestar fcr-grandine
30-
(Rust) (Java) (Nim) (TypeScript) (Rust)
29+
┌──────────┬──────────┬───┴───────┬──────────┬──────────
30+
fcr-lighthouse fcr-teku fcr-nimbus fcr-lodestar fcr-grandine fcr-prysm
31+
(Rust) (Java) (Nim) (TypeScript) (Rust) (Go)
3132
```
3233

3334
Per sim slot N the engine: fetches block N, imports it, looks up `plan[N].source_block_slot`, extracts that block's attestations, injects them using its native equivalent of Lighthouse's `AttestationFromBlock::True`, runs `recompute_head_at_slot(N+1)`, emits one JSONL record. The orchestrator validates each record, enriches with engine metadata, writes the final CSV / JSONL / manifest.
@@ -43,7 +44,7 @@ The orchestrator owns which block sources attestations for each sim slot:
4344
- **`next-non-missed`** (default): for sim slot N, the first non-missed block in `N+1..N+lookahead-cap`. `--lookahead-cap=4` reproduces today's Lighthouse behavior; `--lookahead-cap=32` covers the spec's full inclusion range.
4445
- **`strict-source-block-k-minus-1`**: source is exactly `N+1` if it exists, else nothing.
4546
- **`greedy-lookahead`**: consumes every non-missed block in `N+1..N+lookahead-cap`, bounded to attestations for the FCR evaluation slot.
46-
- **`xatu-first-seen-singles`**: serves per-validator first-seen gossip votes from parquet instead of sourcing attestations from blocks. The orchestrator filters by `raw_seen_ms <= --attestation-first-seen-deadline-ms` (default `12000`) and sends committee-free `attesting_indices` to the engine for direct fork-choice injection. All 5 engines implement this injection path. Use `--attestation-first-seen-base` as either a local base path or `s3://bucket/prefix`; files are expected below `network=<network>/source=raw/epoch=<epoch>/data.parquet`. `--lookahead-cap` is inert in this mode.
47+
- **`xatu-first-seen-singles`**: serves per-validator first-seen gossip votes from parquet instead of sourcing attestations from blocks. The orchestrator filters by `raw_seen_ms <= --attestation-first-seen-deadline-ms` (default `12000`) and sends committee-free `attesting_indices` to the engine for direct fork-choice injection. All 6 engines implement this injection path. Use `--attestation-first-seen-base` as either a local base path or `s3://bucket/prefix`; files are expected below `network=<network>/source=raw/epoch=<epoch>/data.parquet`. `--lookahead-cap` is inert in this mode.
4748

4849
## Build
4950

@@ -60,6 +61,7 @@ bash engines/teku/build.sh # Java, ~5 min cold (needs JDK 25)
6061
bash engines/nimbus/build.sh # Nim, ~5-10 min cold
6162
bash engines/lodestar/build.sh # Node, ~3-5 min cold (needs Node 24+, Corepack)
6263
bash engines/grandine/build.sh # Rust, ~10-15 min cold
64+
bash engines/prysm/build.sh # Go, unreleased PR (downloads its pinned Go toolchain)
6365
```
6466

6567
The orchestrator auto-runs `engines/<engine>/build.sh` if the corresponding binary is missing.
@@ -133,4 +135,4 @@ An older lighthouse-only run is mirrored at `data.ethpandaops.io/fcr-simulator/l
133135

134136
- [Fast Confirmation Rule spec (consensus-specs#4747)](https://github.com/ethereum/consensus-specs/pull/4747)
135137
- [Research paper (arXiv:2405.00549)](https://arxiv.org/abs/2405.00549)
136-
- Per-client FCR PRs: [Lighthouse #8951](https://github.com/sigp/lighthouse/pull/8951), [Teku confirmation-2](https://github.com/Nashatyrev/teku/tree/confirmation-2), [Nimbus (merged to unstable)](https://github.com/status-im/nimbus-eth2), [Lodestar #8837](https://github.com/ChainSafe/lodestar/pull/8837), [Grandine #656](https://github.com/grandinetech/grandine/pull/656), [Prysm #15164 (deferred)](https://github.com/OffchainLabs/prysm/pull/15164)
138+
- Per-client FCR PRs: [Lighthouse #8951](https://github.com/sigp/lighthouse/pull/8951), [Teku confirmation-2](https://github.com/Nashatyrev/teku/tree/confirmation-2), [Nimbus (merged to unstable)](https://github.com/status-im/nimbus-eth2), [Lodestar #8837](https://github.com/ChainSafe/lodestar/pull/8837), [Grandine #656](https://github.com/grandinetech/grandine/pull/656), [Prysm #17122](https://github.com/OffchainLabs/prysm/pull/17122)

engines/prysm/build.sh

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env bash
2+
# Build the Prysm-backed FCR simulator adapter.
3+
set -euo pipefail
4+
5+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6+
ROOT="$(cd "${HERE}/../.." && pwd)"
7+
PRYSM_DIR="${HERE}/prysm"
8+
PACKAGE_DIR="${PRYSM_DIR}/testing/spectest/shared/common/forkchoice"
9+
ADAPTER_DST="${PACKAGE_DIR}/fcr_simulator_adapter_test.go"
10+
BRIDGE_DST="${PRYSM_DIR}/beacon-chain/blockchain/fcr_simulator_bridge.go"
11+
OUT="${ROOT}/results/fcr-prysm"
12+
13+
if [ ! -f "${PRYSM_DIR}/go.mod" ]; then
14+
echo "prysm submodule missing at ${PRYSM_DIR}; run: git submodule update --init --recursive" >&2
15+
exit 1
16+
fi
17+
18+
cleanup() {
19+
rm -f "${ADAPTER_DST}" "${BRIDGE_DST}"
20+
}
21+
trap cleanup EXIT
22+
23+
cp "${HERE}/fcr_simulator_adapter_test.go" "${ADAPTER_DST}"
24+
cp "${HERE}/fcr_simulator_bridge.go" "${BRIDGE_DST}"
25+
mkdir -p "$(dirname "${OUT}")"
26+
27+
echo "[build] compiling Prysm FCR adapter -> ${OUT}" >&2
28+
(
29+
cd "${PRYSM_DIR}"
30+
GOTOOLCHAIN=auto go test -c -o "${OUT}" ./testing/spectest/shared/common/forkchoice
31+
)
32+
33+
echo "[build] done: ${OUT}" >&2

0 commit comments

Comments
 (0)