diff --git a/CLAUDE.md b/CLAUDE.md index 3eaa869..47d05e7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,6 +54,8 @@ Network configuration is controlled via `params.yaml`: - `use_static_keys`: Use pre-computed keys (default: true, ~40s faster) - `boole_epoch`: Boole fork activation epoch - `network.network_params.fulu_fork_epoch`: Set >0 to test Electra→Fulu transition +- `network.network_params.gloas_fork_epoch`: Gloas (ePBS) activation; the sole Gloas switch (ePBS is gated on the Ethereum fork; no SSV-side fork knob). See `params-gloas.yaml` +- `register_validators`: Register keyshare validators on-chain (default false; aetheria registers its own) - `monitor.enabled`: Enable monitoring stack - `images.*`: Docker image overrides diff --git a/Makefile b/Makefile index 7fcd0dc..72a2099 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ ENCLAVE_NAME?=localnet PARAMS_FILE?=params.yaml SSV_NODE_COUNT?=4 SSV_COMMIT?=stage +ANCHOR_COMMIT?=unstable default: run @@ -78,13 +79,17 @@ prepare-ssv: @echo "Building SSV image..." @cd ../ssv && docker build -t node/ssv . +# prepare-anchor builds node/anchor at the FRESHEST commit for ANCHOR_COMMIT (branch, tag, or +# commit; default unstable), detaching like prepare-ssv. Note this moves ../anchor's checkout. .PHONY: prepare-anchor prepare-anchor: @if [ ! -d "../anchor" ]; then \ echo "Cloning Anchor repo..." && \ git clone https://github.com/sigp/anchor.git ../anchor; \ fi - @cd ../anchor && git fetch origin && git checkout origin/unstable + @echo "Checking out Anchor $(ANCHOR_COMMIT) at its freshest commit..." + @cd ../anchor && git fetch origin --tags --force && \ + ( git checkout --detach "origin/$(ANCHOR_COMMIT)" 2>/dev/null || git checkout --detach "$(ANCHOR_COMMIT)" ) @echo "Building Anchor image..." @cd ../anchor && docker build -f Dockerfile.devnet -t node/anchor . @@ -185,10 +190,13 @@ help: @echo "Network scenarios:" @echo " make run Default: Fulu (all forks active)" @echo " make run-boole Boole fork at epoch 3, Fulu at epoch 5" + @echo " make run-gloas Gloas (ePBS) at genesis, all-Anchor cluster" + @echo " make run-gloas-builders Gloas at genesis + buildoor ePBS builders" @echo " make run PARAMS_FILE=custom.yaml Custom params" @echo "" @echo "Configuration:" - @echo " SSV_COMMIT=main make prepare Use a specific SSV branch" + @echo " SSV_COMMIT=main make prepare Use a specific SSV branch" + @echo " ANCHOR_COMMIT=epbs make prepare-anchor Use a specific Anchor branch" @echo "" @echo "Static keys:" @echo " make generate-keys Regenerate static operator keys + keyshares" @@ -203,6 +211,17 @@ run-boole: ensure-keys @echo "──── Starting SSV testnet (Boole fork at epoch 3) ────" kurtosis run --enclave $(ENCLAVE_NAME) --args-file params-boole.yaml . +# Gloas (ePBS) profiles need a Gloas-capable Anchor image: ANCHOR_COMMIT=epbs make prepare-anchor +.PHONY: run-gloas +run-gloas: ensure-keys + @echo "──── Starting SSV testnet (Gloas/ePBS at genesis, all-Anchor cluster) ────" + kurtosis run --enclave $(ENCLAVE_NAME) --args-file params-gloas.yaml . + +.PHONY: run-gloas-builders +run-gloas-builders: ensure-keys + @echo "──── Starting SSV testnet (Gloas/ePBS at genesis + buildoor builders) ────" + kurtosis run --enclave $(ENCLAVE_NAME) --args-file params-gloas-builders.yaml . + # ── Tests ──────────────────────────────────────────────────────────── .PHONY: test-faulty-el diff --git a/README.md b/README.md index 182947b..89e6149 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,54 @@ ssv-mini restart # Rebuild + restart SSV nodes only ssv-mini logs # Tail SSV node 0 logs ``` +## Gloas (ePBS) testnet + +Run an all-Anchor cluster on a Gloas / EIP-7732 (ePBS) chain, for testing Anchor's `epbs` branch +through block proposal and the new 3s attestation deadline. Uses nethermind + lighthouse +`glamsterdam-devnet-5` images (geth has no Glamsterdam build). + +```bash +ANCHOR_COMMIT=epbs make prepare-anchor # build node/anchor from the epbs branch (Rust build, slow) +make run-gloas # or: make run-gloas-builders +make logs SERVICE=anchor-node-0 # tail an operator (no ssv-node-0 in these profiles) +``` + +Two profiles: +- **`params-gloas.yaml`** (`make run-gloas`): Gloas at genesis, 4-operator all-Anchor cluster, + keyshare validators registered on-chain. The core profile for proposal tests (attestation timing + needs an anchor image carrying the #1061 fix; see below). +- **`params-gloas-builders.yaml`** (`make run-gloas-builders`): adds 2 genesis ePBS builders plus + buildoor, exercising external bids, payload reveals, and chain-level PTC (`payload_attestations`). + +### Notes and edge cases + +- **Use a recent Kurtosis CLI.** 1.15.x fails on a `GpuConfig` builtin in the pinned + `ethereum-package`; 1.19.x works (`brew upgrade kurtosis-tech/tap/kurtosis-cli && kurtosis engine restart`). +- **The profiles import a forked `ethereum-package` pin** (`shane-moore/ethereum-package`, by commit). + Kurtosis fetches it automatically, no action needed. The fork exists because Gloas genesis needs + `ethereum-genesis-generator >= 6.0.0` (no tagged release ships it), plus a one-line fix so genesis + ePBS builders do not collide with the preregistered keyshare validators (indices 64-73). +- **Gloas is driven by `gloas_fork_epoch`; SSV attestations need the #1061 fix in your anchor image.** + ePBS activates purely from the Ethereum Gloas fork; there is no SSV-side fork knob. Block proposals, external + builder bids, and chain-level PTC work whenever Gloas is active. SSV cluster attestations also need + an anchor image carrying [sigp/anchor#1061](https://github.com/sigp/anchor/issues/1061): with Gloas + active the committee runs `GloasBeaconVote` for attestations, but the sync-committee path still runs + `BeaconVote`, so the two no longer share one committee QBFT instance. The attestation consensus still + reaches COMMIT, but the sync consensus round-changes every slot and never completes, and because the + committee's post-consensus partial-signature batch is sized to attestations + sync messages, it never + fills, so the agreed attestation is never submitted. Measured live on an `epbs` image without #1061: + proposals canonical, attestations 0. Consensus succeeds; only submission is blocked. +- **`make prepare-anchor` moves `../anchor`'s checkout** to a detached HEAD at `origin/epbs`. If you + keep local work in `../anchor`, branch or stash it first (commits are not lost, but HEAD relocates). +- **All-Anchor cluster** (`ssv.count: 0`): no `node/ssv` image is needed, so skip `make prepare`. + Monitor is disabled in these profiles and the cluster does not need it; leave it off. +- **Builders live in the Gloas `BeaconState.builders` registry**, not the validator set. Query there, + not `/eth/v1/beacon/states/head/validators`. +- **Cluster slots are execution-empty by design (for now).** Anchor proposers self-build and never + reveal the payload (SIP-94 envelope stub), so cluster-proposed slots advance the CL but not the EL, + and PTC votes `payload_present=false` on them. Expected, not a failure: the gating work is Anchor's + bid-targeting path, not envelope signing. + ## All Commands ``` @@ -55,6 +103,8 @@ make help |---------|-------------| | `make run` | Start testnet (default: Fulu, all forks active) | | `make run-boole` | Start with Boole fork at epoch 3, Fulu at epoch 5 | +| `make run-gloas` | Start with Gloas (ePBS) at genesis, all-Anchor cluster | +| `make run-gloas-builders` | Start with Gloas at genesis + buildoor ePBS builders | | `make reset` | Clean + restart from genesis | | `make show` | Show running services and ports | | `make logs` | Tail ssv-node-0 logs (`SERVICE=ssv-node-1` for others) | @@ -94,11 +144,17 @@ network: boole_epoch: 3 # Omit for pre-Boole use_static_keys: true # false = regenerate keys at runtime (~40s slower) + +register_validators: true # Register keyshare validators on-chain (default false; the + # aetheria executor registers its own). Required for standalone + # runs where SSV/Anchor nodes should actually perform duties. ``` Pre-built configs: - `params.yaml` — Fulu at genesis (default) - `params-boole.yaml` — Electra→Boole→Fulu fork transitions +- `params-gloas.yaml` — Gloas (ePBS) at genesis, all-Anchor cluster (needs `ANCHOR_COMMIT=epbs make prepare-anchor`) +- `params-gloas-builders.yaml` — Gloas at genesis + buildoor ePBS builders (experimental) ```bash make run PARAMS_FILE=params-boole.yaml diff --git a/contract/interactions.star b/contract/interactions.star index 448346e..940d09a 100644 --- a/contract/interactions.star +++ b/contract/interactions.star @@ -39,9 +39,9 @@ def register_operators(plan, public_keys, network_address): # register_validators bulk-registers the keyshare validators on-chain via the ethers script # (register-validators.cjs) in a node-based service with the keyshares mounted. # -# UNUSED: validator pre-registration is skipped on local_testnet (main.star Step 4) — the aetheria -# executor registers and funds its own validators. Retained (with register-validators.cjs) pending -# devnet pre-registration, tracked under #29. +# Gated behind `register_validators` in params (main.star Step 4, default off): the aetheria +# local_testnet flow skips it (the executor registers and funds its own validators; see #29), +# while standalone runs (e.g. the Gloas/ePBS profiles) enable it so duties fire. def register_validators(plan, keyshare_artifact, network_address, token_address, rpc, genesis_constants, args): plan.add_service( name="register-validator", diff --git a/contract/registration/register-operators.cjs b/contract/registration/register-operators.cjs index 3bf17c4..ca0ae1c 100644 --- a/contract/registration/register-operators.cjs +++ b/contract/registration/register-operators.cjs @@ -13,6 +13,11 @@ const OUT_FILE = process.env.OPERATOR_DATA_FILE || "/app/operator_data.json"; const OPERATOR_FEE = 1000000000n; // 1 SSV (smallest unit) const MAX_OPERATOR_FEE = 76528650000000n; +// Explicit gas limit: nethermind's eth_estimateGas underestimates SSVNetwork's +// delegatecall-heavy paths (observed: 160k estimate vs 494k actual on registerOperator, +// inner delegatecall OOG via the 63/64 rule -> revert). ethers v6 sends the raw estimate +// as gasLimit, so override it. Harmless on geth. +const GAS_LIMIT = 1500000n; async function main() { const abi = JSON.parse(fs.readFileSync("/app/abis/SSVNetwork.json", "utf8")); @@ -23,13 +28,13 @@ async function main() { const publicKeys = JSON.parse(fs.readFileSync(KEYS_FILE, "utf8")).publicKeys; const coder = ethers.AbiCoder.defaultAbiCoder(); - await (await ssv.updateMaximumOperatorFee(MAX_OPERATOR_FEE)).wait(); + await (await ssv.updateMaximumOperatorFee(MAX_OPERATOR_FEE, { gasLimit: GAS_LIMIT })).wait(); const operators = []; for (const pk of publicKeys) { // The operator public key (RSA PEM string) is ABI-encoded as `bytes`, matching SSV tooling. const encoded = coder.encode(["string"], [pk]); - const receipt = await (await ssv.registerOperator(encoded, OPERATOR_FEE, false)).wait(); + const receipt = await (await ssv.registerOperator(encoded, OPERATOR_FEE, false, { gasLimit: GAS_LIMIT })).wait(); let id; for (const log of receipt.logs) { try { diff --git a/contract/registration/register-validators.cjs b/contract/registration/register-validators.cjs index d7d4117..a241f03 100644 --- a/contract/registration/register-validators.cjs +++ b/contract/registration/register-validators.cjs @@ -1,7 +1,9 @@ // Registers SSV validators on the (v2.0.0) SSVNetwork via ethers — replaces the foundry // RegisterValidators.s.sol. Reads keyshares (shares[].payload.{publicKey,sharesData,operatorIds}), // approves the SSV token, and bulk-registers them into a fresh cluster. -// Note: v2.0.0's bulkRegisterValidator dropped the `amount` param (4 args, not 5). +// Note: v2.0.0's bulkRegisterValidator dropped the `amount` param (4 args, not 5) and is +// PAYABLE: the cluster is funded with native ETH via msg.value, and a zero-value call +// reverts with InsufficientBalance() (0.01 ETH suffices on the local net; we send 1). const fs = require("fs"); const { ethers } = require("ethers"); @@ -11,6 +13,11 @@ const NETWORK_ADDR = process.env.SSV_NETWORK_ADDRESS; const TOKEN_ADDR = process.env.SSV_TOKEN_ADDRESS; const KEYSHARES_FILE = process.env.KEYSHARES_FILE || "/app/keyshares/out.json"; +// Explicit gas limit: nethermind's eth_estimateGas underestimates SSVNetwork's +// delegatecall-heavy paths (see register-operators.cjs), and bulkRegisterValidator +// carries ~10 validators of sharesData. ethers v6 sends the raw estimate as gasLimit. +const GAS_LIMIT = 10000000n; + async function main() { const abi = JSON.parse(fs.readFileSync("/app/abis/SSVNetwork.json", "utf8")); const provider = new ethers.JsonRpcProvider(RPC); @@ -25,8 +32,11 @@ async function main() { // Fresh cluster (never registered for this owner+operators). const cluster = { validatorCount: 0, networkFeeIndex: 0, index: 0, active: true, balance: 0 }; - await (await token.approve(NETWORK_ADDR, ethers.parseEther("1"))).wait(); - await (await ssv.bulkRegisterValidator(publicKeys, operatorIds, sharesData, cluster)).wait(); + await (await token.approve(NETWORK_ADDR, ethers.parseEther("1"), { gasLimit: 200000n })).wait(); + await (await ssv.bulkRegisterValidator(publicKeys, operatorIds, sharesData, cluster, { + gasLimit: GAS_LIMIT, + value: ethers.parseEther("1"), + })).wait(); console.log("Registered " + publicKeys.length + " validator(s)"); } diff --git a/main.star b/main.star index 34917a8..9a7a947 100644 --- a/main.star +++ b/main.star @@ -1,6 +1,12 @@ -ethereum_package = import_module("github.com/ethpandaops/ethereum-package/main.star@6.1.0") -input_parser = import_module("github.com/ethpandaops/ethereum-package/src/package_io/input_parser.star@6.1.0") -genesis_constants = import_module("github.com/ethpandaops/ethereum-package/src/prelaunch_data_generator/genesis_constants/genesis_constants.star@6.1.0") +# Pinned to a fork of ethereum-package main @ d47e987 (2026-06-11) plus one fix, branch +# shane-moore/ethereum-package@ssv-mini/builder-skip-preregistered. Why not a release: Gloas +# (ePBS) genesis needs ethereum-genesis-generator >= 6.0.0 and the latest tagged release +# (6.1.0) still pins 5.3.5. Why the fork: upstream's builder_count derives genesis builders +# (and buildoor's key) starting at the participant validator sum, colliding with our +# preregistered keyshare validators 64-73; the fix skips past preregistered_validator_count. +ethereum_package = import_module("github.com/shane-moore/ethereum-package/main.star@24e306ff4cd575417b9163d8e6738d740d0c133a") +input_parser = import_module("github.com/shane-moore/ethereum-package/src/package_io/input_parser.star@24e306ff4cd575417b9163d8e6738d740d0c133a") +genesis_constants = import_module("github.com/shane-moore/ethereum-package/src/prelaunch_data_generator/genesis_constants/genesis_constants.star@24e306ff4cd575417b9163d8e6738d740d0c133a") ssv_node = import_module("./nodes/ssv/node.star") anchor_node = import_module("./nodes/anchor/node.star") blocks = import_module("./blockchain/blocks.star") @@ -118,11 +124,24 @@ def run(plan, args): plan.remove_service(constants.ANCHOR_KEYSPLIT_SERVICE, description="Cleaning up keysplit service") # ── Step 4: Register validators on-chain ── - # Skipped on the v2.0.0 contracts: the aetheria executor registers and funds its own validators - # (registration is payable/msg.value on v2.0.0), and its event flow expects a clean, empty - # cluster. Devnet pre-registration of the static keyshares (the payable-deposit path) is tracked - # in ssvlabs/ssv-mini#29. - plan.print("Step 4/5: Skipping validator pre-registration (executor registers its own; see #29)") + # Default off: the aetheria executor registers and funds its own validators (registration is + # payable/msg.value on v2.0.0), and its event flow expects a clean, empty cluster (see #29). + # Standalone runs (no aetheria, e.g. the Gloas/ePBS profiles) set register_validators: true so + # the keyshare validators are registered to the operator cluster and duties actually fire. + if args.get("register_validators", False): + plan.print("Step 4/5: Registering keyshare validators on-chain") + interactions.register_validators( + plan, + keyshare_artifact, + constants.SSV_NETWORK_PROXY_CONTRACT, + constants.SSV_TOKEN_CONTRACT, + el_rpc, + genesis_constants, + args, + ) + plan.remove_service("register-validator", description="Cleaning up validator registration service") + else: + plan.print("Step 4/5: Skipping validator pre-registration (executor registers its own; see #29)") # ── Step 5: Start SSV and Anchor nodes ── node_index = 0 diff --git a/nodes/anchor/node.star b/nodes/anchor/node.star index b91e557..936af22 100644 --- a/nodes/anchor/node.star +++ b/nodes/anchor/node.star @@ -15,8 +15,14 @@ def start(plan, num_nodes, cl_url, el_rpc, el_ws, key_pems, config, image): "--logfile-max-number", "0", "--debug-level", "debug", # mitigation of https://github.com/sigp/anchor/issues/765 "--subscribe-all-subnets", + # Prometheus metrics (duty timing, head-event trigger counters, QBFT instrumentation) + "--metrics", "--metrics-address", "0.0.0.0", "--metrics-port", "5164", ] + metrics_ports = { + "metrics": PortSpec(number=5164, transport_protocol="TCP", application_protocol="http"), + } + plan.add_service( name="anchor-node-0", description="Starting Anchor bootnode (node 0)", @@ -25,6 +31,7 @@ def start(plan, num_nodes, cl_url, el_rpc, el_ws, key_pems, config, image): entrypoint=["anchor"], cmd=command_arr, files=files, + ports=metrics_ports, private_ip_address_placeholder=IP_PLACEHOLDER, ready_conditions=ReadyCondition( recipe=ExecRecipe( @@ -54,6 +61,7 @@ def start(plan, num_nodes, cl_url, el_rpc, el_ws, key_pems, config, image): entrypoint=["anchor"], cmd=command_arr_with_boot, files=files, + ports=metrics_ports, private_ip_address_placeholder=IP_PLACEHOLDER, ) plan.add_services(remaining_configs, description="Starting {} remaining Anchor nodes in parallel".format(num_nodes - 1)) diff --git a/params-gloas-builders.yaml b/params-gloas-builders.yaml new file mode 100644 index 0000000..a2abc43 --- /dev/null +++ b/params-gloas-builders.yaml @@ -0,0 +1,76 @@ +# Gloas (ePBS / EIP-7732) at GENESIS with external builders — experimental profile. +# ethereum-package only registers genesis ePBS builders (0x03 withdrawal credentials) when +# gloas_fork_epoch is 0, so this profile forfeits fork-transition testing (use params-gloas.yaml +# for that) in exchange for the external-builder bid path: buildoor submits ePBS bids, and the +# chain-level PTC (the lighthouse VC's validators 0-63) attests payload timeliness. +# Anchor-side PTC duties stay inert until sigp/anchor lands the PayloadAttestationService +# spawn (#1078) and sign_payload_attestation (#1082). +# +# Gloas/ePBS activates from network_params.gloas_fork_epoch (the Ethereum fork); there is no +# SSV-side fork knob (Gloas-era SSV consensus is gated on the eth ChainSpec). Boole stays at epoch 0. The +# external-builder bids and chain-level PTC this profile tests work whenever Gloas is active, +# independent of the SSV committee consensus type. (SSV cluster attestations additionally need an +# Anchor image with the sigp/anchor#1061 fix, else the unmigrated sync path stalls the committee +# partial-signature batch and attestations are never submitted; see params-gloas.yaml.) +# +# Requires a Gloas-capable Anchor image: ANCHOR_COMMIT=epbs make prepare-anchor +# Usage: make run PARAMS_FILE=params-gloas-builders.yaml + +network: + participants: + - el_type: nethermind + el_image: ethpandaops/nethermind:glamsterdam-devnet-5 + cl_type: lighthouse + cl_image: ethpandaops/lighthouse:glamsterdam-devnet-5 + validator_count: 32 + count: 2 + supernode: true + + additional_services: + - dora + + # buildoor: self-contained builder+relay with ePBS bidding (epbs_builder defaults true). + mev_type: "buildoor" + + network_params: + network_id: "3151908" + deneb_fork_epoch: 0 + electra_fork_epoch: 0 + fulu_fork_epoch: 0 + gloas_fork_epoch: 0 + # Genesis ePBS builders with 0x03 withdrawal credentials (requires gloas at epoch 0). + builder_count: 2 + perfect_peerdas_enabled: false + # Same layout as params.yaml: VCs run [0,64), genesis deposits [0,74); the SSV operators + # adopt the deposited-but-VC-idle validators 64-73 (registered on-chain in Step 4 here, + # since register_validators is enabled). + preregistered_validator_count: 74 + + port_publisher: + cl: + enabled: true + +use_static_keys: true + +# Standalone run (no aetheria executor): register the static keyshare validators (indices +# 64-73) into the operator cluster so the Anchor nodes actually have duties. +register_validators: true + +monitor: + enabled: false + +boole_epoch: 0 + +nodes: + ssv: + count: 0 + anchor: + count: 4 + +images: + ssv: "node/ssv" + anchor: "node/anchor" + monitor: "monitor" + redis: "redis:7.4.2" + postgres: "postgres:15" + deployer: "localssv/ssv-network" diff --git a/params-gloas.yaml b/params-gloas.yaml new file mode 100644 index 0000000..4b29e5b --- /dev/null +++ b/params-gloas.yaml @@ -0,0 +1,79 @@ +# Gloas (ePBS / EIP-7732) at GENESIS: every epoch is Gloas, so attestation/proposal duty +# timing (the 3.0s ATTESTATION_DUE_BPS_GLOAS deadline) is measurable from the first slots. +# +# Gloas/ePBS activates purely from network_params.gloas_fork_epoch (the Ethereum fork). There +# is no SSV-side fork knob: Gloas-era SSV consensus is gated on the eth ChainSpec (gloas_enabled()), +# so with gloas_fork_epoch: 0 the cluster runs GloasBeaconVote committee consensus from genesis. +# +# Block proposals work whenever Gloas is active (the proposer path is version-driven, keyed off +# the block the BN returns). SSV cluster ATTESTATIONS additionally require an Anchor image that +# carries the sigp/anchor#1061 fix: with Gloas active, attestations run GloasBeaconVote while the +# sync-committee path still runs BeaconVote, so they no longer share one committee QBFT instance. +# The attestation consensus reaches COMMIT, but the sync consensus round-changes forever and the +# committee partial-signature batch (sized to attestations + sync) never fills, so the agreed +# attestation is never submitted (measured live on an epbs image without #1061: 0 attestations +# published). +# External-builder bids and chain-level PTC are testable regardless of #1061. +# To test the fork TRANSITION instead, set gloas_fork_epoch (and boole_epoch, strictly increasing +# when not 0) to e.g. 3. +# +# Requires a Gloas-capable Anchor image: ANCHOR_COMMIT=epbs make prepare-anchor +# Usage: make run PARAMS_FILE=params-gloas.yaml +# +# geth has no Glamsterdam build, so the EL is nethermind (the devnet-5 pairing). The monitor +# stays off: E2M cannot parse Fulu+ blocks, and no SSV (go) nodes run here — the cluster is +# all-Anchor, since only Anchor's epbs branch speaks Gloas. + +network: + participants: + - el_type: nethermind + el_image: ethpandaops/nethermind:glamsterdam-devnet-5 + cl_type: lighthouse + cl_image: ethpandaops/lighthouse:glamsterdam-devnet-5 + validator_count: 32 + count: 2 + supernode: true + + additional_services: + - dora + + network_params: + network_id: "3151908" + deneb_fork_epoch: 0 + electra_fork_epoch: 0 + fulu_fork_epoch: 0 + gloas_fork_epoch: 0 + perfect_peerdas_enabled: false + # Same layout as params.yaml: VCs run [0,64), genesis deposits [0,74); the SSV operators + # adopt the deposited-but-VC-idle validators 64-73 (registered on-chain in Step 4 here, + # since register_validators is enabled). + preregistered_validator_count: 74 + + port_publisher: + cl: + enabled: true + +use_static_keys: true + +# Standalone run (no aetheria executor): register the static keyshare validators (indices +# 64-73) into the operator cluster so the Anchor nodes actually have duties. +register_validators: true + +monitor: + enabled: false + +boole_epoch: 0 + +nodes: + ssv: + count: 0 + anchor: + count: 4 + +images: + ssv: "node/ssv" + anchor: "node/anchor" + monitor: "monitor" + redis: "redis:7.4.2" + postgres: "postgres:15" + deployer: "localssv/ssv-network"