Skip to content

feat: fast_forward, start a network already finalized - #1510

Open
samcm wants to merge 30 commits into
mainfrom
fast-forward
Open

samcm wants to merge 30 commits into
mainfrom
fast-forward

Conversation

@samcm

@samcm samcm commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Adds fast_forward, which starts a fresh kurtosis network already finalized: genesis goes in the past, timeskip generates every slot since then against a private reth, the chain goes into every beacon node, and the validator clients take over at a slot boundary with slashing protection for the generated history. until sets where the generated history ends, as a fork or epoch plus a state (activating, activated, justified or finalized); activating hands over slots_before slots (default 10) ahead of the fork, so the transition runs live. With the default history: checkpoint, beacon nodes checkpoint-sync from the generated finalized checkpoint and only the later blocks are published, which keeps long histories such as Gloas at epoch 1000 practical. A Gloas-at-genesis Lighthouse + Prysm network comes back from kurtosis run finalized in about 50 s, instead of after about 26 minutes.

samcm added 23 commits October 21, 2026 17:06
…offset and live cut-off; assert the target after generation
Envelopes go with Eth-Blob-Data-Included: true and empty blobs, as an
outside publisher must, and with broadcast_validation=consensus, since
Grandine only imports them on that path. Each envelope is confirmed by
GET of the envelope, or for Prysm by its EL head reaching the payload,
which also stops Prysm's goroutine-sent forkchoice updates from
reordering. Gloas blocks are posted bare. The report records each
node's EL head, since Grandine leaves it one payload behind until the
next head change.
…tional services

main.star returns early when additional_services is empty, before the
wait_for_finalization block, so the per-node target check never ran in
the usual fast_forward config. The check now runs before that return.
…insertion

In a Fulu-genesis, Gloas-at-epoch-3 run, Nimbus, Lighthouse and Grandine
reached the generated head within 5 s while Prysm was at block 12.
Prysm's resyncIfBehind saw peers two epochs ahead and reverted to
initial sync, so it answered 503 and range-synced the rest, reaching the
head after 276 s with a 23-slot handover gap. Lighthouse did the same in
another run: a peer more than 32 slots ahead made it open a head-chain
range sync that never completed, leaving is_syncing true. Pacing the
fast nodes keeps every peer within one epoch, so neither triggers.
…t times, pick the handover slot once every slow step is done, start VCs just before it; Grandine loads its in-process keys through the keymanager
…the generated chain's last finalized checkpoint

fast_forward.history picks how beacon nodes get the generated history:
- checkpoint (default): ff-gen runs before any beacon node, while
  checkpoint.py feeds every EL the payloads up to the generator's current
  finalized checkpoint (and finally up to the anchor, never past it, with a
  forkchoice update every 32 payloads). The anchor block, post-state and
  envelope plus the blocks after it are stored; ff-checkpoint serves the
  anchor (and the genesis state for Prysm) as a beacon API, every CL
  checkpoint-syncs from it, and insert.py publishes only the tail, starting
  with the anchor's envelope.
- full: the previous path, publishing every block from genesis.
A chain with no finalized checkpoint after genesis (history ending before
epoch 3) falls back to full.

Also: ff-reth gets a 64 MB MDBX growth step (the default 4 GB step filled the
container layer) and is removed once generation ends, the generated history
is deleted from the run's container after use, the live fork cut-off uses
ff-gen --max-slot so generation keeps following the wall clock, and the
generation and insert waits scale with the history length.
In the checkpoint path the payloads, blocks and envelopes before the
finalized checkpoint are never read again once every EL has them, so the
feeder deletes them as it goes and the run's disk use stays at a few epochs
of files instead of growing with the history.
…s its root

After checkpoint sync the anchor's children carry an older justified
checkpoint than the anchor, so Lighthouse imports them without making them
head; waiting for the head slot made it time out on the first tail block.
Teku checkpoint-syncs from the state alone, has no anchor block, and answers
400 to the anchor's envelope; it still imports the tail. Post the anchor
envelope once with a short wait, record a rejection, and carry on.
…sses the payload

When later blocks arrive by gossip, Prysm's EL head can move past a payload
between two polls, so waiting for the head to equal it timed out and stopped
the publish to that node. Accept a head at or beyond the payload's number
whose chain holds the payload's hash.
history: checkpoint now ends in the same glide-path handover as history: full. Both
modes generate 32 slots past the wall clock (timeskip --ahead-slots), publish up to
the wall slot, and drip the rest at slot times. The tail keeps interchange.json and
attesters.json for the drip's watermark. The generator is the timeskip image
(ghcr.io/ethpandaops/timeskip), with TIMESKIP_MNEMONIC and a python3 entrypoint
for the checkpoint server.
@redpandabot

This comment has been minimized.

@redpandabot

This comment has been minimized.

@redpandabot

This comment has been minimized.

@redpandabot redpandabot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Adds fast_forward: genesis is placed in the past, timeskip generates the history against a private reth, ELs are fed (checkpoint path) or blocks are inserted (full path), and VCs take over at a slot boundary with imported slashing protection. The fast-forward machinery itself checks out against the real timeskip CLI/output contract and is internally consistent. The real problem is unrequested scaffolding that silently changes default behavior for kt- remote-enclave networks, activating duplicate validators on a live source enclave.

Issues

  • 🔴 src/package_io/input_parser.star:1797 — kt- networks no longer auto-zero validator counts, silently activating duplicate validators — The zeroing exclusion plus remote_enclave.star:68 generating keystores whenever a mnemonic exists (always — preregistered_validator_keys_mnemonic is validated non-empty) means every kt- run now boots in-process validators derived from the same mnemonic the source enclave runs, with no slashing-protection import on this path (load_in_process_keys only runs when fast_forward is enabled, which resolve_fast_forward forbids for kt-). B's validators duplicate A's live validators and can double-propose, slashing them and forking the source chain — contradicting README.md:200 ('Validator counts are auto-zeroed for kt- networks, so B is observer-mode by default'), which this PR does not update. This should be gated on an explicit opt-in or reverted.
  • 🟡 src/participant_network.star:259 — ff-el-feed-gate hijacks checkpoint_sync_url on kt- networks — see the thread on that line

Reviewed @ ec07b02d
"Weeks of coding can save you hours of planning."

constants.NETWORK_NAME.remote_enclave
) and args_with_right_defaults.checkpoint_sync_url.startswith("http"):
plan.run_sh(
name="ff-el-feed-gate",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 ff-el-feed-gate hijacks checkpoint_sync_url on kt- networks

For any kt- network with checkpoint_sync_url set (e.g. pointing at the source enclave's beacon API), the launch now blocks forever on wget {url}/ff/ready and fails after the 20 m wait, instead of checkpoint-syncing as before. The gate should key off a dedicated fast-forward input, not reuse checkpoint_sync_url.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant