Period Store

Period Store

Features

When using the prover as a remote prover, there are serveral features, which will be supported.

1. Historical Proofs

When creating proofs for block older than the current sync_committee you can't prove them with the current keys unless you use the historical_summaries in the state of the Beacon chain. In order to do this, you will not only need a beacon API capable of providing proof for the historical summaries, which are currently only supported by Lodestar and nimbus, but also require all 8192 blockroots in order to build the merkle-proof. In order to be able to build those proofs on demand, the server manages a period_store, where those block roots are stored in one file. (blocks.ssz) The server is also keeping track of new heads on continue to write those block roots so it can create those historical proofs when needed. This would hardly be possible on a local device.

2. ZK-Proofs for Sync-Committee Transitions.

The verifier always needs the 512 pubkeys of the current sync_committee in order to verify the signatures of the blocks. In order to get them, there are 2 ways:

  1. setting or getting a current checkpoint from a checkpoint provider and the getting the proof from a beacon API for the sync_committees keys (light client bootstrap). But of checkpoint would need to come from a external source you need to trust. And if your client was offline for a longer time, you would need to use the light_client_updates, where each would be 25kB per 27h, to prove the transitions to the current keys.

  2. create a hardcoded fixed trust anchor and the use a zero-proof to prove all the transitions from that point to the current sync_committee. This also allows to create such a prove offline and is independent of how old this trsut anchor is. Of course in both cases a weak subjectivity check may be needed, but even this can be done with signed checkpoints, so you would be able to create proofs without relying on external source and so support offline verification.

Architecture

A period is a contiguous range of 8192 beacon slots (≈ 27h on mainnet).

  • Period number: period = slot >> 13 (equivalent to slot / 8192).

  • The server maintains a local on-disk directory called the period store.

  • The period store enables:

    • Historical proofs (needs all block roots of a period to build Merkle proofs)

    • Light client caching (LCU/LCB per period)

    • ZK proofs for sync committee transitions (pre-generated by the master)

Directory layout

period_store is a directory with one subdirectory per period:

Files (what they mean)

  • blocks.ssz

    • 8192 block roots, each 32 bytes → max ~256 KiB.

    • Written incrementally slot-by-slot.

    • Required to build historical Merkle proofs.

  • headers.ssz

    • 8192 beacon block headers, each 112 bytes → max ~896 KiB.

    • Written incrementally slot-by-slot.

    • Used for integrity checks and to access historical headers.

  • blocks_root.bin

    • Marker file: contains the Merkle root of blocks.ssz.

    • Created only after blocks.ssz was verified against historical_summaries.

    • Semantics: if present, blocks.ssz/headers.ssz for that period are considered finalized and should not change.

  • historical_root.json

    • Proof response containing the historical_summaries (used to verify blocks.ssz).

  • lcu.ssz

    • Light client update for the period.

  • lcb.ssz

    • Light client bootstrap for the first finalized block in that period.

  • ZK-related files (produced by scripts/run_zk_proof.sh):

    • sync.ssz: prepared input data (temporary, may exist only during proof generation)

    • zk_pub.bin: public input data (e.g. [SRC_PUBKEY_HASH:u256 | DST_PUBKEY_HASH:u256 | DST_PERIOD:u64 | ATTESTED_HEADER_ROOT:u256 | DOMAIN:bytes32])

    • zk_proof.bin: SP1 core proof

    • zk_proof_g16.bin: Groth16 proof (used by the verifier)

    • zk_vk.bin, zk_vk_raw.bin, zk_groth16.bin: verification keys / proof artifacts

    • zk_proof.ssz: Packs zk_proof_g16.bin into the SSZ container used for sync-data delivery (ZKSyncData, see C4_ETH_REQUEST_SYNCDATA_UNION[2] in src/chains/eth/ssz/verify_types.c). This also includes the next checkpoint and all headers needed to prove the attestedHeader.

Writer model

Head tracking (continuous)

On every new head event, the server writes:

  • the slot’s block root into blocks.ssz

  • the slot’s header into headers.ssz

This happens for the current period and is safe against reorgs by overwriting the slot position.

Backfill (startup + periodic)

Backfill exists to quickly populate the last N periods after startup (or after falling behind).

Configuration:

  • period_backfill_max_periods (C4_PERIOD_BACKFILL_MAX_PERIODS)

  • period_backfill_delay_ms (C4_PERIOD_BACKFILL_DELAY_MS)

How it works (high level):

  • once the master-server receives the first head and knows the latest block root, it will trigger a backfill, checking whether all the root doen to max_periods are stored and correct. Missing blocks will be fetched from the beacon API.

  • A delay is applied between backfill requests to avoid public API rate limits.

Blocks root verification (blocks_root.bin)

Once historical_root.json exists for a historic period, the server can verify:

  • hash-tree-root(blocks.ssz) == expected root from historical_summaries

If the verification succeeds, it writes blocks_root.bin as an immutable marker.

Master / Slave modes

The mode is determined by configuration:

  • Master mode: period_store configured and period_master_url not set.

    • Writes period store data locally.

    • Serves period store over HTTP. (/period_store)

    • Generates ZK proofs.

  • Slave mode: period_store configured and period_master_url set.

    • Does not generate ZK proofs.

    • Fetches data from the master.

    • Optionally performs full_sync.

HTTP API: /period_store

The server exposes the period store via /period_store.

File download

  • offset is optional.

  • If provided, bytes are served starting at that offset (used for resumable downloads).

Manifest (SSZ)

Returns application/octet-stream containing an SSZ list of:

  • period: uint64

  • filename: SSZ_STRING(32) (null-terminated)

  • length: uint32

Notes:

  • The master fails fast if period directory gaps are detected.

  • The handler caches small manifest responses for ~30s to reduce repeated scanning from multiple slaves.

  • Individual files larger than a small safety limit (currently 5 MiB) are treated as an error for the manifest.

Slave sync: lazy vs full sync

Lazy fetch (default)

When a slave needs a file that is missing locally, it performs an on-demand download from the master and stores it in its own period_store.

This minimizes transfer volume but does not guarantee a full backup.

Full sync (optional)

If enabled, the slave tries to keep a complete local replica of the master’s period store.

Configuration:

  • C4_PERIOD_FULL_SYNC=1 / period_full_sync=1

  • PERIOD_MASTER_URL / period_master_url=<master_url>

Trigger:

  • The full sync is triggered on finalized checkpoints (≈ every 6 minutes on mainnet).

Algorithm (high level):

  1. Determine last_full_period by scanning local directories from newest to oldest until a “complete” period is found:

    • blocks_root.bin present

    • zk_proof_g16.bin present

  2. Request manifest from master:

  1. For each manifest entry, download the file if needed:

    • If local size == manifest length: skip

    • If local size < manifest length: download with ?offset=<local_size> and append

    • If local size > manifest length: full download (truncate)

Micro-fork edge case:

  • If blocks_root.bin exists for a period on the master, blocks.ssz and headers.ssz are always re-downloaded fully (truncate) to avoid missing silent corrections.

Failure handling:

  • If a download/write fails for a period that already looks “complete”, the slave removes blocks_root.bin so the period will be revisited on the next sync.

ZK proofs: generation and configuration

ZK proofs are generated only on the master.

Trigger

On each finalized checkpoint, the master checks whether the next period’s proof exists and is valid.

  • Target period for proving: target_period = current_period + 1

  • Proof files:

    • zk_proof_g16.bin

    • zk_pub.bin

If a proof exists:

  • It is verified.

  • If invalid:

    • If “fresh” (< 1h): do not re-prove (to avoid cost loops)

    • If “old”: delete and retry

Configuration

  • PERIOD_PROVER_KEY_FILE / period_prover_key_file

    • Path to a file containing the SP1/Network private key (e.g. via Docker secrets).

    • The server passes this path to the proof script via SP1_PRIVATE_KEY_FILE=<path>.

Script execution

The master spawns scripts/run_zk_proof.sh (or /app/run_zk_proof.sh in Docker) with arguments similar to:

The script:

  • Loads keys from SP1_PRIVATE_KEY_FILE (or /run/secrets/... fallbacks)

  • Uses prebuilt artifacts in Docker if present (/app/eth_sync_program, /app/eth-sync-script)

  • Produces the proof artifacts inside <period_store>/<period>/...

Minimal configuration examples

Master

Slave (lazy fetch only)

Slave (full sync)

Last updated