> For the complete documentation index, see [llms.txt](https://corpus-core.gitbook.io/specification-colibri-stateless/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://corpus-core.gitbook.io/specification-colibri-stateless/introduction/architecture/dpn.md).

# Prover Network

**Design & Architecture — Working Draft**

> Status: architecture proposal, not yet implemented. Scope: the fallback/redundancy layer for colibri-stateless verifiers and the coordination model for sync-committee zk-proof production across independent provers.

***

## 1. Motivation

colibri-stateless solves **trust and verification**: a verifier — typically running as a library inside a dApp, most often in the browser — can cryptographically check every piece of data it receives, so it never has to trust the source of that data. Whatever a prover, a Beacon-API, or an execution RPC returns is either provably correct or rejected.

colibri does **not** solve **liveness**. To produce a verifiable answer the verifier still needs *data*:

* the **beacon block header** and the **BLS `sync_aggregate`** signing it,
* **receipts** (for `eth_getTransactionReceipt`, `eth_getBlockReceipts`, `eth_getLogs`),
* **state proofs** (`eth_getProof`, storage slots for local `eth_call`),
* and, when the verifier does not yet hold the relevant `SyncCommittee`, the **recursive zk-proof** that proves the committee for the current period.

Today this data comes from one of:

1. a **prover** (single request, brings everything needed to verify — including the recursive zk-proof when the verifier lacks the committee), or
2. a **Beacon-API + execution client** directly.

Every one of these can fail. A single configured prover, or a single Beacon-API endpoint, is a **single point of failure (SPOF)**. If it is down, the dApp stops — even though colibri itself is working perfectly.

**The DPN exists to remove that SPOF without reintroducing a trust assumption.** The design goal is: when the primary prover is unreachable, the dApp keeps working — slower, possibly with reduced functionality, but working — by falling back to a set of independent provers.

***

## 2. Threat model

Because colibri verifies everything, the set of things a malicious or faulty fallback prover can do to us is small and well-bounded. This is the single most important fact in the whole design, because it determines how much machinery the network actually needs.

| Capability of a hostile/faulty prover | Possible? | Mitigation                                                                                                                                                                                   |
| ------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Lie** (return incorrect data)       | **No**    | The verifier checks every proof. This is colibri's core value and it holds fully here.                                                                                                       |
| **Stall / be slow / time out**        | Yes       | Costs time, not correctness. Handled by trying the next prover.                                                                                                                              |
| **Censor** (omit data)                | Partly    | For `eth_getLogs` only in the positive Bloom-match case; the negative case is a hard proof. See §7.                                                                                          |
| **Serve stale-but-valid data**        | Yes       | **Already mitigated**: colibri's freshness check — the proof carries the block header timestamp, which is checked against a validity window. A prover that simply stops syncing is detected. |
| **Observe user queries**              | Yes       | **Already mitigated** by PAP (Pragmatic Adaptive Privacy): sensitive request parameters never reach the prover in the first place.                                                           |

### The real worst case is out of DPN scope

The one attack that genuinely breaks a verifier is a **supply-chain compromise** of the client itself — e.g. a replaced `fetch` implementation that feeds fabricated data into, or swaps the result out of, the verification path. This sits **on the client side of verification**, so no prover-list design can defend against it: an attacker running code in the dApp's browser context has already won.

The correct defence for that threat is integrity anchoring *outside* mutable JS — Subresource Integrity on the WASM blob, reproducible builds, a signed verifier module whose check cannot itself be bypassed by a `fetch` hijack. **This is a separate work package and must not be entangled with the DPN.** Spending DPN complexity on a threat the DPN cannot cover is wasted effort.

### Consequence for the design

The registry needs **no Byzantine fault tolerance**. It needs only:

* **Sybil-resistance** against "flood the list with garbage URLs", and
* **enforceable freshness** so stale provers are detectable.

That is a dramatically cheaper requirement than a trustless P2P data network, and it is why the DPN can remain a *list with a ranking*, not a consensus system.

***

## 3. Why not an existing P2P network

Two candidates were evaluated in depth as browser-side fallbacks: the **Portal Network** and the execution-layer **devp2p** (`eth`/`snap`) network. Both were rejected. The reasoning is worth recording because it is the justification for building something new.

### 3.1 The browser transport wall

Both networks are fundamentally unreachable from a browser tab:

* **Portal Network** runs over **discovery v5 + uTP**, i.e. raw **UDP**. The browser has no UDP API. WebTransport is client→server against an HTTP/3 server with a valid certificate; Portal nodes are not that. WebRTC DataChannel can do P2P but cannot perform a discv5 handshake with an arbitrary node.
* **devp2p** (`eth`/`snap`) runs over **RLPx**, i.e. a raw **TCP** socket with an ECIES handshake, plus discv4/v5 on UDP. Equally impossible from the browser.

In **both** cases a browser participant needs a **proxy** that speaks the raw protocol on its behalf (this is exactly what the Ultralight browser-client does with its UDP proxy). But once a proxy is mandatory, the trust model collapses to **"trust a gateway"** — which is precisely the model the DPN already provides with a list of HTTP(S) provers, only without the extra protocol surface. A proxy buys nothing over a gateway list here.

### 3.2 Portal Network — specific findings

The Portal Beacon Chain network *does* expose the light-client content types (`LightClientBootstrap` `0x10`, `LightClientUpdatesByRange` `0x11`, `LightClientFinalityUpdate` `0x12`, `LightClientOptimisticUpdate` `0x13`, `HistoricalSummariesWithProof` `0x14`), and for this sub-network radius is explicitly *not* applicable — every peer holds everything, so a single reachable peer suffices. That part is attractive.

But it does not solve our actual problem:

* **The trust anchor is unchanged.** A Portal node only accepts a `LightClientBootstrap` it can map to a **trusted block root** provided by config. Portal replaces the *transport* of the bootstrap, not the *anchor*. We still need our checkpointz list. (This matches our current design, so it is no regression — but it is no gain either.)
* **State retrieval is impractical.** The Execution State network stores individual **trie nodes**; `FINDCONTENT`/`FOUNDCONTENT` payloads contain **no proofs** (a piece of state can exist under many roots), so the caller must walk the MPT sequentially. An `eth_getProof` becomes \~12–15 strictly sequential DHT lookups — seconds to tens of seconds. The state spec is still under `legacy/` and its rollout stalled on gossip performance.
* **Signed headers for arbitrary past slots are unavailable.** Finality/optimistic updates are ephemeral; nodes keep only the latest.
* **Maturity is unverified for our content.** Glados (the official monitor) reports \~100% audit pass rates — but its dashboard explicitly audits only **pre-merge bodies and receipts**. It says nothing about the Beacon network, the State network, or post-merge history — exactly the three things we would depend on. Operator diversity (as opposed to the five-client *implementation* diversity: Trin, Nimbus, Samba, Shisui, Ultralight) is also unverified; if most nodes are EF/Status-operated, a Portal fallback would move the SPOF, not remove it.

Portal is **validation-first** and therefore philosophically aligned with colibri, and a Portal node behind our verifier would be a coherent *origin* option — but it is not a browser fallback for our full data need.

### 3.3 devp2p `eth`/`snap` — and why it is reinvented LES

* `GetNodeData` was **removed in `eth/67`** (EIP-4938); the state tree is no longer retrievable via `eth`.
* `snap` offers range queries with boundary proofs (`GetAccountRange`, `GetStorageRanges`) — effectively an `eth_getProof` anchored to a `rootHash` — **but** snapshot data is served only for the **last 128 blocks**. Historical proofs are impossible.
* **`snap/2`** (EIP-8189, June 2026) **removed `GetTrieNodes`**, replacing iterative trie healing with block-access-list application. The granular trie-node query is gone.
* Decisively, the `snap` spec states its `eth`-coupling exists specifically **"to avoid non-full-nodes from lingering attached to `snap` indefinitely."** In other words, a light, non-contributing leech is the exact thing the protocol was designed to prevent.

A light client that selectively asks full nodes for headers, receipts and state proofs and gives nothing back **is LES**. LES existed, too few full nodes served it, and it was **removed from Geth**. Abusing `snap` as "LES 2.0" — even as a rarely-triggered fallback — would, if it ever triggered at scale, have thousands of dApps consuming the peer slots of validators and full nodes, destabilising the very network they depend on. This path is not "abuse that probably works"; it is a path the community already tried, rejected, and dismantled. The Portal Network *is* the community's answer to this exact question.

### 3.4 Conclusion

The problem is **redundancy without trust**, not **P2P data distribution**. colibri already supplies the "without trust" half. The honest solution to the redundancy half is a **list of independent operators**, tried in turn — the same trick as our checkpointz list, extended to block data. A prover needs only a few GB, so the population able to run one is orders of magnitude larger than for Beacon+Execution full nodes, which is what makes a list viable.

***

## 4. Architecture overview

Two independent layers:

1. **Client-side fallback selection.** Each verifier has a **primary prover** (configured, paid-for, or self-operated) and a **cached list of fallback provers**. It uses the network only when the primary fails. (§7)
2. **Prover-side coordination.** Provers form a loosely-connected network that (a) maintains and gossips the membership list, and (b) coordinates production of the per-period sync-committee zk-proof so that, normally, exactly one prover builds it. (§5, §6)

### The artifact split

A prover produces two fundamentally different kinds of output, and they have opposite distribution characteristics:

* **(a) The recursive `SyncCommittee` zk-proof, one per period (\~27 h).** Identical for every consumer, immutable, content-addressable (hash of period → proof). Expensive to build (SP1 proving network, \~2 min, and a real monetary cost even if small). Self-verifying, so the verifier checks it regardless of source. → **belongs on dumb, cacheable mirrors** (CDN / S3 / object storage / IPFS), *not* on "master provers you beg via RPC". A prover that serves 500 peers' proofs by RPC is a new LES server; a CDN object is not.
* **(b) Per-request data** (header + `sync_aggregate`, receipts, Merkle branches from `eth_getProof`). Needs live nodes, but is cheap per request: fetch + Merkle assembly, no zk. **Marginal cost ≈ zero.**

This split is the economic foundation of the whole network: **the expensive thing is a public good with near-zero marginal cost of distribution, and the per-request thing is cheap enough to give away.** A fallback prover serving a thousand foreign requests has built zero extra zk-proofs; it has done a little RPC and Merkle assembly. That cost structure is what makes mutual-insurance viable without anyone having to be altruistic. **Separate "who&#x20;*****builds*****&#x20;the proof" (few, costly, may fail) from "who&#x20;*****distributes*****&#x20;the built proof" (arbitrarily many, free); as long as both live in the same prover process, distribution inherits the scarcity of production.**

***

## 5. The sync-committee zk-proof: necessity and production

### 5.1 Why the zk-proof is required (not optional)

It is tempting to think checkpoint + bootstrap alone would suffice. It does not, for three reasons:

1. **Instant current-period verification.** After \~4 weeks of a wallet being closed, verifying the current `SyncCommittee` from updates alone means downloading and verifying \~25 `LightClientUpdate`s *plus* a checkpoint, because the starting point is outside the Weak Subjectivity window. The zk-proof lets the verifier validate the current period immediately.
2. **Security against cheap forgery.** With only checkpoint + bootstrap, the critical weakness is *how the checkpoint is obtained*. An attacker who generates 512 arbitrary keys and builds a bootstrap proving them as the `next_sync_committee` needs only to get the matching checkpoint onto a widely-used checkpointz server to make **any reality** cryptographically verifiable. With a zk-proof that proves the full chain of transitions, the attacker's only remaining option is a **real long-range attack** — requiring 2/3 of the validators of a *former* sync committee who have all since exited — **and** the checkpointz must still be convinced, because a zk-proof is *additionally* checked against the checkpoint (WSP).
3. **Offline verification.** The zk-proof enables a proof that can be verified **completely offline** — e.g. a door lock checking a smart contract for access, talking only to an app over Bluetooth. The lock cannot fetch checkpoints, so **signed checkpoints are embedded in the proof**, and the lock holds a list of public keys whose signatures it trusts.

Reason 2 is what makes the *distribution* of proof-building **security-relevant**, not merely a UX concern: if no honest prover builds and everyone degrades to bootstrap, the security level of the entire user base for that period drops to "512-fake-key attack suffices". The build-coordination mechanism therefore keeps the security level up, it does not only smooth UX.

### 5.2 Production is a dedup problem, not an incentive problem

Because the bootstrap path exists as a safety net, a missing zk-proof is **not catastrophic** — it is a (security-degraded) fallback, not an outage. This reframes the whole problem:

* We do **not** need to guarantee that *someone* builds. We need *most of the time* someone builds, without *everyone* building simultaneously.
* The classic free-rider trap ("all wait for each other → nobody acts") largely dissolves here: the cost is trivial (\~10 ¢ / 27 h, below the threshold at which strategic free-riding is worth the thought); the builder has a **private** benefit (its own dApp needs the proof); and the proof is **self-verifying**, so the builder pool is fully **permissionless and anonymous** with no Sybil risk on the build side — a forged proof simply fails verification.
* A known counter-intuitive result (the volunteer's dilemma) is that with *more* symmetric, anonymous, coordination-free players, the probability that *anyone* volunteers can *fall*. Our escape is **asymmetry**: the prover whose dApp needs the proof next has the strongest incentive. The mechanism must preserve that asymmetry, not erase it with anonymous symmetry.

### 5.3 The timing structure is favourable

When period X begins, the `next_sync_committee` for **X+1** is already fixed in the beacon state. The proof for X+1 is therefore **buildable a full period (\~27 h) before it is needed** (at the start of X+1). This is a large buffer, not a tight deadline: the proof for X+1 can sit finished on the mirrors long before any wallet asks for it. The "nobody built in time" case is structurally almost impossible in normal operation — it requires that *every* honest, capable prover was unreachable for the entire 27 h, which the bootstrap net still covers.

### 5.4 The epoch-slot mechanism

Rather than wall-clock timers (which create a race whose width is the build time plus mirror propagation), the mechanism uses **epochs as discrete slots**.

Proof-building is already tied to finality checkpoints: when the first finality checkpoint of a period arrives (\~every 6 min ≈ one epoch), the prover checks whether a proof for that period already exists. So each prover **picks a random epoch within the 256 epochs of the sync period** as its intended build slot. At that epoch, it first tries to fetch the proof from any peer/mirror; **only if it cannot fetch it does it build.**

This converts a continuous race into a **discrete birthday problem**: N provers, 256 slots. The build time (\~2 min) fits inside one epoch (\~6.4 min), so the grid spacing exceeds the build time — the collision window is closed. Two provers only contend if they pick the **same epoch**, not merely nearby times.

**Birthday math** (expected colliding pairs ≈ N² / (2·256) = N² / 512):

| Provers N | Expected colliding pairs        |
| --------- | ------------------------------- |
| 16        | \~0.5 (practically never)       |
| 23        | \~1                             |
| 50        | \~4.9                           |
| 150       | \~22                            |
| 256       | \~128 (nearly every slot taken) |

256 slots is not arbitrary — it is the number of epochs the period *has*, and it is well-dimensioned for realistic network sizes (tens to low hundreds of provers). A collision does not mean a double build: a **second random number (tie-breaker)** decides which of two same-slot provers actually builds. A double build only survives when the tie-break exchange did not arrive in time — and that costs \~10 ¢, which is acceptable.

### 5.5 Early-weighted slot distribution

Early and late slots are **not** security-equivalent. A proof built in epoch 10 exists early, so every wallet waking in that period immediately gets the strong zk-path. A proof built in epoch 250 leaves a long window in which no proof exists and every waking wallet falls back to the weaker bootstrap path (the §5.1 reason-2 downgrade). **The later the slot, the longer the entire user base is exposed to the cheap-forgery risk.**

Therefore the slot choice is **not** uniform over \[0, 256]. It is a **falling distribution weighted toward the first \~32–64 epochs**: this brings the proof out within the first few hours of the boundary (good for security) while leaving enough spread to keep the collision rate low. The long tail of late epochs is purely a **liveness backstop** for the case where all early builders fail — it guarantees *someone* builds before the period ends. One distribution serves both goals: early-weighting for security, long tail for liveness.

The exact shape trades three quantities against each other and should be computed against an expected N: **security** (how early the proof exists), **collision rate** (how often double builds happen), and **communication load** (how large the relevant peer suffix is, see §6). More provers → must spread wider → gives up some earliness. With the expected 100–150 provers, a distribution concentrated in the first \~32–64 epochs is the starting point.

### 5.6 Coordination without reliable announcements

The key property: **no reliable "I am building now" broadcast is required.**

* Because slots are epoch-based, a prover only needs to know that *someone with the same or an earlier slot* intends to build. A prover with an earlier slot has either already built (then you fetch) or has failed (then you build anyway).
* Announcements of build-intent are therefore **best-effort**: if one is lost, the worst case is a double build (\~10 ¢). No retries, acks, or guaranteed delivery.
* **Once the proof is built, no further build-intent announcements are needed at all** — the proof's presence on the mirrors is itself the signal.
* The only mechanism that must be *reliable* is purely local and needs no communication: **"if at my chosen epoch I cannot fetch the proof from anyone, I build it."** This cannot be broken by lost messages and guarantees that some honest, live prover eventually builds within the 27 h.

**Denial-of-build safety.** A build-intent hint must **delay, never gate**. A forged "I'm building" claim from an attacker who then never builds must not stop an honest prover from building — otherwise it becomes a network-wide security downgrade (§5.1 reason 2). Since the mechanism is epoch-based and the fetch-or-build rule is unconditional at the chosen epoch, this is handled naturally: the claim is an efficiency hint, not a lock, and the self-verifying proof means nobody has to believe the claim anyway.

### 5.7 Config change: `MASTER_URL` → `FALLBACK_URL`

Today a single `MASTER_URL` means "this prover may rest and never build zk-proofs; when it needs one and does not find it on disk, it fetches it from the master." The master also collects checkpointz signatures and block roots (for `historical_summaries` proofs of old blocks).

The proposed change makes all provers **equal**. `MASTER_URL` becomes a symmetric `FALLBACK_URL`: "I nominate this peer as my fallback, and it may use me as its fallback too." If unset, the prover knows it must handle everything itself. This is the entry point into the membership network (§6).

***

## 6. Membership & gossip

### 6.1 Bootstrapping and list propagation

Each prover configures **one** explicit fallback peer (`FALLBACK_URL`). On startup the two exchange their membership lists via a dedicated endpoint. This is an **epidemic / anti-entropy rumor protocol**: when a prover learns a URL it did not have, it adds it and forwards the update to the others; a peer that already holds the newest list does not need to re-forward. The list converges to a common view in O(log N) rounds.

### 6.2 Self-validating admission (Sybil-resistance by capability)

A prover admits another to its list **only if it has itself fetched and verified a correct, fresh proof from that peer within the last&#x20;*****n*****&#x20;minutes.** Sybil-resistance is thus by **capability, not cost**: to appear in the list you must actually operate a working prover. This is the same "validation-first" spam defence the Portal Network uses. A list entry is therefore not just `{url}` but roughly:

```
{ url, role, chain_id, last_verified_slot, last_verified_at, observed_latency }
```

where `last_verified_at` is a claim the *verifier* can cheaply re-check.

### 6.3 Hard admission filters: HTTPS + CORS

Two browser constraints are **admission requirements**, not nice-to-haves — and both are familiar from colibri's own deployment work:

* **Valid HTTPS certificate.** A dApp served over `https://` cannot call an `http://` prover (mixed content). Every fallback prover needs a domain and a certificate.
* **CORS.** This is the subtle one: a prover can look perfect in node-to-node gossip (which has no CORS) and be **silent in the browser**. Admission must therefore include a **server-side `OPTIONS` preflight with a foreign `Origin`**, admitting the peer only if `Access-Control-Allow-Origin: *` comes back. Otherwise the failure surfaces at the party least able to fix it — the end user's browser.

These two filters mean "permissionless join" is in practice restricted to operators with a real domain and correct CORS — which is fine, and keeps the network small and competent.

### 6.4 Fanout cap and staggered intent

At the expected 100–150 provers, full-mesh membership is comfortable and **no hierarchy / Kademlia is needed**. If a cap is wanted, cap the **fanout**, not the list length: forward updates to *k* random peers (k ≈ 3–4). The list itself may be complete; only the *talking* about it is bounded.

Build-intent messages are naturally sparse: a prover need only announce to the peers with the **same or a later slot** (only they could collide with it or wait on it), and it announces **one epoch before** its intended slot. Combined with the early-weighted distribution, almost all intent traffic concerns the early epochs where the fewest peers are still relevant, so the communication load thins out exactly where it would otherwise concentrate. Once the proof is built, intent traffic stops entirely.

### 6.5 Blacklist: local and short-lived

A prover that fails to respond is dropped by the verifier — but the blacklist must be **strictly verifier-local and TTL-bounded**:

* **Do not share blacklists.** A shared/reported blacklist is a distributed-blacklisting vector: an attacker answers the health-checkers slowly and selectively and gets honest provers ejected network-wide.
* **Asymmetry between checker and user.** Prover A may verify B server-side successfully while a browser cannot reach B (CORS, geoblocking, IPv6-only, corporate proxy). A local, short-lived blacklist tolerates this; a shared one weaponises it.
* A blacklisted prover must get another chance after minutes (TTL), or the list degrades monotonically.
* **Grey failure** (a prover slow on 30 % of requests) passes a binary liveness check and is worse than a hard-down one. Selection therefore needs a **latency percentile per prover**, measured by the verifier itself — not a mere liveness bit.

***

## 7. Client-side load policy

### 7.1 Fallback-first, not load-balancing

The default is **primary prover for everything; network only as a safety net.** This is an economic and privacy conclusion, not merely a technical one.

**Economics.** Distributing all requests across the whole network by default creates an inverted tragedy of the commons:

* An **RPC-provider** runs a prover to *sell* it. If the default streams non-customers' load to whoever has the most spare capacity, it punishes the biggest contributor with the most foreign load — proportional to its generosity. The rational response is aggressive rate-limits against non-customers, or leaving the network. Both harm it.
* A **dApp running its own prover** has *dimensioned* it for its own load and budget. Streaming its own requests elsewhere means paying for unused capacity while its load sits with strangers. It *wants* its requests to go to its own prover.

Both operator types therefore want the *same* thing: **primary + network-as-safety-net.** This is not a compromise; it is the shared preference.

**Privacy.** Every prover a request reaches sees something (within what PAP permits) about user activity. One request to a deliberately-trusted primary is a controlled flow; the same requests sprayed across 100 rotating strangers is the opposite of what PAP is for. Load-balancing over strangers and privacy are directly opposed, and colibri has already chosen privacy.

### 7.2 Three client profiles

| Profile                                  | Behaviour                                                                                                                                                                                 |
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Has a configured/paid primary**        | All requests → primary. On primary failure, fall back to the **public** pool, choosing per request by latency-weighted random selection (Power-of-Two-Choices), with backoff on HTTP 429. |
| **Has no own prover** (many small dApps) | Distribute from the start, but **only over the public pool**, Power-of-Two on measured latency, 429 backoff.                                                                              |
| **Dedicated foreign provers**            | Carried by nobody except their own customers — which protects their business model and keeps them in the network.                                                                         |

When the primary is down and the client spreads over the public pool, spreading *is* healthy — but note that a primary is usually chosen for performance, and public provers are typically slower and rate-limited, so this is genuinely a degraded mode, not an equivalent one.

### 7.3 Capacity heterogeneity: measure, don't self-report

Do **not** model self-reported capacity — it is unverifiable, instantly stale, and everyone has an incentive to misstate it (an RPC-provider overstates to win customers; an overloaded hobby prover understates for self-protection). Use only what the client can **measure without trusting anyone**: **observed latency** and **observed rejection (429)**. A slow or rate-limiting prover disqualifies itself in real time, and those signals are unforgeable because they are behaviour, not claims.

The **one** qualitative bit worth transmitting is a **role flag: `public/best-effort` vs `dedicated/paid`.** It is not a capacity number, it does not go stale, and nobody has an incentive to lie (a dedicated provider *wants* to be recognised as "come to me only if you pay"). This flag is the axis the entire load policy hangs on: it decides **whom the fallback mechanism may burden at all.** It is the only self-reported field in the system that can be trusted.

### 7.4 No API keys in the shared list

When a client uses an RPC-provider as its primary, it registers that prover **with an API key**. The provider may hand the client a fallback list, but **that list must never contain API keys** — at most it advertises a rate-limited public prover. Credentials stay with the primary configuration and never propagate through the membership gossip.

***

## 8. Multi-chain considerations

The mechanism is parameterised by the chain's period/epoch structure. **Gnosis Chain** has different epoch timing than Ethereum mainnet, but the model is unchanged: the slot space is "the epochs of one sync period", the build-time-fits-in-one-epoch property must be re-checked against the chain's epoch duration, and the early-weighting is expressed in epochs rather than wall-clock. Each chain runs its own DPN membership scoped by `chain_id` (already an entry field), and provers only peer with same-chain provers.

***

## 9. Independent networks

Separate prover networks may form — an RPC-provider and its customers naturally cluster; a group of cooperating dApps forms another. This is **healthy, not a problem.** Because membership is propagated transitively, clusters merge wherever operators cross-register as each other's fallback, and stay separate where they deliberately do not. Global connectivity need not be forced. The only care needed: a client given a list from network A must not assume network B's provers are reachable/intended for it — but the same latency/429 measurement handles this automatically, ejecting an unresponsive foreign-cluster prover after the first timeout.

***

## 10. Implementation phasing

**Do not start with the gossip protocol.** Build the value first, add the decentralisation when the numbers justify it.

**Phase 1 — signed manifest + mirrored proofs (\~90 % of the value).** A signed JSON manifest of prover URLs — `{ url, role, chain_id, last_seen, cors_verified }` — hosted on **N independent mirrors**. The verifier fetches it from any mirror, checks the signature, caches it. A health-checker (may run in our infrastructure) refreshes it hourly. The per-period sync-committee proofs go on the **same mirrors**, content-addressed. This has a central signer — but correctness is protected by colibri regardless, and liveness is protected by the mirrors. Estimated a couple hundred lines.

**Phase 2 — gossip with self-validating membership.** Only once there are >50 provers and the central signer genuinely becomes a constraint. Adds the symmetric `FALLBACK_URL` peering, self-validating admission, and best-effort intent gossip (§6). Before that threshold it is machinery without payoff — a Kademlia for a dozen nodes.

> **Guiding principle:** the DPN protects against **remote** failure and corruption (prover down, stalling, censoring). Against **local** corruption (the supply-chain worst case) it is powerless by construction, so it must spend no complexity there. Do not build a cathedral for a shed that already has an awning.

***

## 11. Open questions

* **Slot-choice distribution shape.** The single decision that simultaneously determines security (how early the proof exists), collision rate, and communication load. Needs to be computed against a concrete expected N (working assumption: 100–150). Uniform-over-256 is the worst choice on the security axis; a falling distribution over the first \~32–64 epochs is the starting proposal.
* **Gossip/mirror propagation speed vs. slot spacing.** If a finished proof reaches everyone in seconds, narrow spreading suffices and double builds are rare; if propagation takes minutes (CDN invalidation, IPFS DHT walk), spreading must be wider. This measured value determines whether anything beyond proactive pre-building is even needed.
* **`eth_getLogs` completeness proof default (issue #128).** Censorship is only possible in the positive Bloom-match case; the negative case is a hard proof (header + Bloom proof that the event *cannot* be present). The full completeness proof closes the positive case but is more expensive. Because a polling wallet hits the positive case rarely — but when it does, it is usually the event the user cares about — the **default** matters more than the option's existence. Options: force the full proof *only when the Bloom matches* (cheap negative case stays cheap, closes the gap almost entirely; feasibility depends on whether the match is detected client-side before the prover answers, which under PAP it may not be — then it is a second round-trip in the rare positive case), or make completeness an **explicit setup decision** per dApp rather than a silent default. Same opt-in/opt-out logic as fallback provision, but with *security* rather than availability at stake.
* **PAP interaction under fallback.** PAP rewrites requests (whole block instead of txhash; local EVM with per-slot verification; Bloom filter instead of address). This makes requests data-heavier but compute-light — ideal for cheap fallback work — but the larger transfer volume can be noticeable over a slow fallback. Worth setting a different UX expectation in fallback mode than for the primary.

***

## References

External facts underpinning the design decisions:

* Portal Network specs — <https://github.com/ethereum/portal-network-specs> (README sub-protocol list; `history/history-network.md`; `legacy/beacon-chain/beacon-network.md`; `legacy/state/state-network.md`; `bootnodes.md`)
* Glados (Portal monitor) — <https://glados.ethportal.net/> and <https://github.com/ethereum/glados>
* Portal History Network FAQ (validation-first / spam resistance) — <https://notes.ethereum.org/@Kolby-ML/HJ-9D5aYp>
* devp2p `eth` capability (eth/67, no state over `eth`) — <https://github.com/ethereum/devp2p/blob/master/caps/eth.md>
* devp2p `snap` capability (snap/2, 128-block retention, boundary proofs, "dependent satellite", `GetTrieNodes` removal / EIP-8189) — <https://github.com/ethereum/devp2p/blob/master/caps/snap.md>
* EIP-4938 — Removal of `GetNodeData` — <https://eips.ethereum.org/EIPS/eip-4938>
* LES removal from Geth (light protocol dropped) — go-ethereum release history
* NIP-65 Relay List Metadata (comparable endpoint-list model) — <https://nips.nostr.com/65>
* "What is the Outbox Model?" (empirical relay-clustering lesson) — <https://www.whynostr.org/post/8yjqxm4sky-tauwjoflxs/>
* colibri PAP specification — <https://corpus-core.gitbook.io/specification-colibri-stateless/specifications/ethereum/pragmatic-adaptive-privacy>
* colibri `eth_getLogs` completeness proof — issue #128, <https://github.com/corpus-core/colibri-stateless/issues/128>

*Threat-model analysis, the (a)/(b) artifact split, the epoch-slot / birthday-problem framing, the early-weighting rationale, and the load-policy reasoning were developed in discussion and are not attributable to a single external source.*
