From 5497e24fef61e8ab726812adad93bb61cdb76fba Mon Sep 17 00:00:00 2001 From: yigitdot Date: Thu, 3 Sep 2026 04:38:14 +0300 Subject: [PATCH 1/6] docs: resync docs.decdn.org with the current protocol spec Co-Authored-By: Claude Opus 5 --- docs/overview/architecture.mdx | 29 ++++++------ docs/overview/design-decisions.mdx | 4 +- docs/overview/glossary.mdx | 30 ++++++++----- docs/overview/how-it-works.mdx | 60 +++++++++++++------------ docs/overview/introduction.mdx | 6 +-- docs/overview/participants.mdx | 12 ++--- docs/protocol/content-addressing.mdx | 34 ++++++++------ docs/protocol/governance.mdx | 30 ++++++++----- docs/protocol/network.mdx | 24 +++++++--- docs/protocol/payments.mdx | 67 ++++++++++++++++++++-------- docs/protocol/privacy.mdx | 24 +++++----- docs/protocol/reputation.mdx | 51 +++++++++++++-------- docs/protocol/slashing.mdx | 26 +++++++---- docs/protocol/takedown.mdx | 38 ++++++++++++---- 14 files changed, 271 insertions(+), 164 deletions(-) diff --git a/docs/overview/architecture.mdx b/docs/overview/architecture.mdx index c758b5d..5e669d9 100644 --- a/docs/overview/architecture.mdx +++ b/docs/overview/architecture.mdx @@ -1,6 +1,6 @@ --- title: Architecture -description: The node, client, and origin-backend roles, plus the full path of a paid delivery — discovery, selection, channel open, streamed bytes, vouchers. +description: The node, client, and origin-backend roles, plus the full path of a paid delivery — discovery, selection, streamed bytes, vouchers, redemption. --- ## System diagram @@ -32,30 +32,27 @@ graph TD C3 -->|"paid delivery"| N3 N2 -.->|opaque fetch| S3 - - N1 <-.->|"gossip"| N2 - N2 <-.->|"gossip"| N3 ``` ## Participant roles -| Role | Operated by | Participates in CDN? | -| ------------------------ | ------------------------------------------------ | --------------------------------------------------------- | -| **Node (pure cache)** | Independent node operator | Yes — bonds, gossips, probes, delivers | -| **Node (origin-backed)** | Content provider _or_ node operator with storage | Yes — same protocol, plus a DAO-recognized origin backend | -| **Client** | End-user or application | Pays for bytes; subscribes to gossip but does not publish | +| Role | Operated by | Participates in CDN? | +| ------------------------ | ------------------------------------------------ | ----------------------------------------------------------- | +| **Node (pure cache)** | Independent node operator | Yes — bonds, probes, publishes to the DHT, delivers | +| **Node (origin-backed)** | Content provider _or_ node operator with storage | Yes — same protocol, plus a publisher-seated origin backend | +| **Client** | End-user or application | Pays for bytes; reads the registry but holds no role | See [Participants](/overview/participants) for a deeper breakdown per role. ## Life of a paid delivery -1. **Client bootstrap** — client generates a keypair, queries the on-chain registry, seeds a peer table, and subscribes to regional + global gossip topics. -2. **Discovery** — client probes its known peers for a blob hash. On miss, any peer performs a DHT lookup to locate holders. +1. **Client bootstrap** — client generates a keypair, queries the on-chain registry for the active node set, and caches it. +2. **Discovery** — a DHT lookup locates holders of the blob hash, and a parallel probe confirms which of them will actually serve the wanted range, at what rate and latency. 3. **Selection** — returned candidates are scored by price, latency, and reputation; lowest cost wins. -4. **Payment channel open** — client opens a USDC payment channel with the selected node on-chain ([payments](/protocol/payments)). -5. **Streaming + vouchers** — node streams to the client; client signs off-chain vouchers as bytes flow. -6. **Cache-miss fan-out** — if the node doesn't have the blob, it pulls from another peer (paid). Every byte delivered in the network is paid. -7. **Channel close** — either party initiates settlement on-chain, with a dispute window for stale closes. +4. **Payment** — the client pays from a USDC pool it opened once, off the fetch path, and reuses for every node ([payments](/protocol/payments)). +5. **Streaming + vouchers** — node streams to the client; client signs a cumulative voucher and releases one hash-chain preimage per megabyte as bytes flow. +6. **Cache-miss fan-out** — if the node doesn't have the blob, it pulls from other holders (paid). Every byte delivered in the network is paid. +7. **Redemption** — the node redeems its own vouchers on-chain whenever it likes; the owner closes the pool to reclaim the unspent remainder. ## Key invariants @@ -64,7 +61,7 @@ See [Participants](/overview/participants) for a deeper breakdown per role. - A node cannot join the mesh without bonding — registration enforces a capacity bond (`bond = k × Mbps^α`) scaled to declared bandwidth, rather than a flat minimum. - A node cannot register an identity it does not control — both keys must sign at registration. - Safety bounds on all governable parameters are hardcoded — governance cannot set fees to 100% or push the operator revenue share below its floor. -- Being recognized as an origin is DAO-governed — configuring an origin backend locally without authorization means the node's bytes are served only as cache. +- Being recognized as an origin is gated on-chain — governance vets a publisher wallet, and the vetted publisher seats its own origin operators per namespace. Configuring an origin backend locally without being seated means the node's bytes are served only as cache. - A node cannot serve a blacklisted hash after the compliance window — doing so is slashable. ## Non-goals diff --git a/docs/overview/design-decisions.mdx b/docs/overview/design-decisions.mdx index 73ebcec..0b2576e 100644 --- a/docs/overview/design-decisions.mdx +++ b/docs/overview/design-decisions.mdx @@ -18,7 +18,6 @@ The protocol is shaped by a set of accepted ADRs. The table below names each one | 012 | Client architecture | | 013 | Schema evolution | | 014 | On-chain slashing verification | -| 015 | QUIC 0-RTT | | 016 | Smart contract interaction | | 017 | Privacy | | 018 | Liquidity strategy | @@ -28,8 +27,9 @@ The protocol is shaped by a set of accepted ADRs. The table below names each one | 026 | Tokenomics | | 028 | Slashing appeals | | 030 | Region self-attestation | -| 031 | Content blacklist appeals | | 036 | Served-bytes voting weight | | 037 | Regional proxy warming | | 038 | Bao verified-range streaming | | 039 | Multi-source parallel fetch | +| 040 | Cache admission & eviction | +| 041 | Refuse-to-serve economics | diff --git a/docs/overview/glossary.mdx b/docs/overview/glossary.mdx index 6b9d9c9..afe412e 100644 --- a/docs/overview/glossary.mdx +++ b/docs/overview/glossary.mdx @@ -1,6 +1,6 @@ --- title: Glossary -description: Definitions for the core deCDN terms — bao verified streaming, blobs, capacity bonds, payment channels, vouchers, probes, nodes, and clients. +description: Definitions for the core deCDN terms — bao verified streaming, blobs, capacity bonds, payment pools, vouchers, namespaces, probes, nodes, and clients. --- | Term | Definition | @@ -8,21 +8,27 @@ description: Definitions for the core deCDN terms — bao verified streaming, bl | **Bao** | The BLAKE3 verified-streaming encoding used for all delivery. Interleaves content with hash proofs so any requested range verifies against the root once aligned to its enclosing 16 KiB chunk-group boundary. | | **Blob** | A content-addressed byte sequence. Every blob is identified by its hash; clients verify received bytes against the known hash. | | **Bond (capacity)** | TOKEN locked in `CapacityBond` proportional to a node's declared bandwidth (`bond = k × Mbps^α`) — the only TOKEN-side requirement to join the mesh; the bond scales with declared capacity rather than a flat minimum. Returned on deregistration after an unbonding period. | -| **Channel (payment)** | On-chain escrow between client and node that backs off-chain vouchers. Opens with a deposit; settles with the highest-nonce voucher after a dispute window. | -| **Client** | A lightweight QUIC endpoint that streams content and pays per MB. No stake, no gossip publish. | -| **DHT** | Kademlia-subset content discovery. Nodes self-publish records when caching a blob. | -| **Full holder** | A node caching a complete blob (as opposed to a partial range). Only full holders are eligible as sources for a multi-source fetch. | -| **Gossip** | Epidemic broadcast over topic channels for node metadata and rate updates. | +| **Capability** | An owner-signed authorization letting one key spend up to a cap from a pool until an expiry. Node-agnostic — valid at every node — and registered on-chain once, on that signer's first redemption. | +| **Chain root** | The head of a hash chain committed in a voucher. The payer releases one **preimage** per megabyte delivered, advancing the amount owed without another signature; a preimage proves its own position by hashing forward to the root. | +| **Client** | A lightweight QUIC endpoint that streams content and pays per MB. No stake, no on-chain registration, no network role. | +| **Coverage** | Which 64 MiB blocks of a blob a holder will serve, carried as a bitmap in DHT records and probe responses. Lets a requester route a wanted range to a holder that actually has it. | +| **Credit window** | The unpaid balance a node will carry on one lane before it pauses delivery. Starts at a floor and ramps as the lane pays, bounding what a non-paying counterparty can extract. | +| **DHT** | Kademlia-subset content discovery. Nodes self-publish records, with coverage, when caching a blob. | +| **Full holder** | A node caching a complete blob, as opposed to a partial holder caching only some blocks. Both are eligible sources for a multi-source fetch. | | **Hash sequence** | An ordered collection of blob hashes (a directory or manifest equivalent). | -| **Multi-source fetch** | Fetching one large blob from several full holders in parallel, each serving bao-aligned work units, up to a configurable maximum (`max_sources`). Each source is an ordinary paid stream — no new wire protocol. | +| **Multi-source fetch** | Fetching one large blob from several holders in parallel, each serving disjoint verifiable ranges, up to a configurable maximum (`max_sources`) and at most one node per operator. Each source is an ordinary paid stream — no new wire protocol. | +| **Namespace** | A publisher-owned identifier for a content set, and the unit of origin addressing. A request pairs it with the blob hash; the default namespace names no publisher and no authorized origins. | | **Node** | A bonded QUIC endpoint that caches and delivers blobs. Some are origin-backed; others are pure caches. | | **Origin-backed node** | A node configured with an S3-compatible store, NFS mount, or local disk — the canonical source for specific blobs. Never experiences a true cache miss. | | **Origin backend** | The opaque storage behind an origin-backed node (S3, R2, B2, MinIO, NFS, local disk). Never exposed to the network. | | **Outboard** | A blob's BLAKE3 hash tree, stored separately from its content. An origin-cold node must import or build the outboard before it can serve verified ranges. | -| **Peer mesh** | The flat set of all bonded nodes. Discovery via gossip; content location via DHT. | -| **Probe** | Parallel latency + availability check that runs before any delivery commitment. | +| **Payment chunk** | The payment quantum: 1 MiB, a protocol constant. One released preimage pays for exactly one chunk. Distinct from the 16 KiB bao chunk group, which is the unit of verification. | +| **Peer mesh** | The flat set of all bonded nodes. Membership comes from the on-chain registry's active set; content location comes from the DHT. | +| **Pool (payment)** | One on-chain USDC deposit that backs off-chain vouchers from many capped signers to many nodes. Opened once by its owner and reused; the owner reclaims the unspent remainder at close, after a grace window that lets nodes redeem first. | +| **Probe** | Parallel latency, availability and coverage check that runs before any delivery commitment. Its signed fields are on-chain slash evidence. | +| **Publisher** | An address that owns at least one namespace. Once governance has vetted the publisher wallet, it seats and unseats the origin operators for its own namespaces directly. | +| **Segment** | A contiguous, verifiably-aligned byte range assigned to a single source during a multi-source fetch. Independently verifiable, so it can be split or reassigned without re-reading from the start. | | **Selection score** | A combined ranking over advertised price, observed latency, and reputation. Lower is better. | | **TOKEN** | The deCDN bonding/governance token: locked as a node's capacity bond and burned by the buyback sink. Not used for payments; governance weight comes from delivered bytes, not TOKEN balance. | -| **USDC** | The payment currency for all delivery. Fixed at deployment; channels deposit, voucher, and settle in USDC. | -| **Voucher** | A signed off-chain payment message. The primary payment instrument between client and node. | -| **Work unit** | A bao-aligned byte range assigned to a single source during a multi-source fetch. Independently verifiable, so it can be re-dispatched or hedged without re-reading from the start. | +| **USDC** | The payment currency for all delivery. Fixed at deployment; pools deposit, voucher, and settle in USDC. | +| **Voucher** | A signed off-chain payment message naming its payee node and carrying a cumulative amount owed. The highest amount settles; a re-submitted voucher pays zero. | diff --git a/docs/overview/how-it-works.mdx b/docs/overview/how-it-works.mdx index 5bb8873..e07384f 100644 --- a/docs/overview/how-it-works.mdx +++ b/docs/overview/how-it-works.mdx @@ -10,17 +10,18 @@ sequenceDiagram autonumber participant C as Client participant N as Node + participant P as PaymentPool (on-chain) C->>N: Probe(hash) - N-->>C: Probe response (has blob, rate) - C->>N: Open on-chain channel (USDC) - C->>N: Stream request(hash) + N-->>C: Probe response (coverage, rate, signed) + C->>N: Stream request(hash, namespace) N-->>C: Bytes (hash-verified on receipt) - C-->>N: Voucher (signed, off-chain) - C->>N: Channel close / settle + C-->>N: Voucher (signed, cumulative) + C-->>N: Preimage per MiB delivered + N->>P: Redeem (voucher + preimage) ``` -The client picks the candidate with the best combination of price, latency, and reputation. After settling on a node, it opens a USDC channel and streams. Vouchers cover every byte delivered; the node can settle any voucher on-chain later. +The client picks the candidate with the best combination of price, latency, and reputation. It pays from a pool it opened once and reuses for every node, so nothing on this path touches the chain until the node chooses to redeem. Vouchers cover every byte delivered. ## Cache miss with pull-through @@ -30,45 +31,47 @@ sequenceDiagram participant C as Client participant N as Node (serving) participant D as DHT peers - participant P as Peer with blob + participant P as Peers with the blob participant O as Origin-backed node - C->>N: Stream request(hash) + C->>N: Stream request(hash, namespace) N->>D: DHT lookup(hash) - D-->>N: Candidate peers + D-->>N: Candidate holders + coverage N->>P: Probe (parallel) - P-->>N: Has blob + P-->>N: Fresh coverage, rate N->>P: Stream request — N pays P P-->>N: Bytes (verified) N-->>C: Stream bytes while pulling (N charges C) - N->>O: Falls back to origin-backed node if no peer has the blob + N->>O: Falls back to an authorized origin if no holder covers the range ``` -Paid delivery is one protocol — used both client→node and node→node. Every byte of the node-to-node pull is paid by the serving node, which amortizes that cost across many downstream client deliveries. +Paid delivery is one protocol — used both client→node and node→node. Every byte of the node-to-node pull is paid by the serving node, which amortizes that cost across many downstream client deliveries. The pull is **demand-windowed**: the node fills only far enough ahead of what its client has consumed and paid for, so a client that abandons at half leaves the node holding the demanded prefix rather than the whole blob. -If pull-through is disabled in the node's config, the node returns a redirect pointing to a peer (never an origin URL). The client opens a channel with that peer directly. +A node may also simply decline. Refusing is not a protocol violation and is not slashable: if the upstream price doesn't clear the margin on the resale, serving at a loss is the wrong move for the operator and the client re-routes to another provider, paying only a latency cost. ## Multi-source parallel fetch -For a large blob, the client doesn't pull from a single node. A client-side scheduler splits the blob across several sources at once — full holders only for now — up to `max_sources` (a configurable cap on parallel sources). Each source is assigned bao-aligned **work units** (byte ranges the bao decoder can verify on their own). +For a large blob, the client doesn't pull from a single node. A client-side scheduler admits a set of holders — **full and partial holders alike**, ranked by the same selection score, at most one node per operator — and fans out to all of them at once, up to `max_sources` (default 4, above a 64 MiB size floor). -Assignment is dynamic: fast sources steal work from slow ones, every unit is verified as it lands, a failed unit is re-dispatched to another source, and the last few stragglers are hedged across sources so one slow peer can't stall completion. Aggregating parallel streams lets a blob be delivered at multi-gigabit speeds once it's cached across enough full holders — no single origin's uplink caps the transfer. +Assignment follows **coverage**: discovery and probes carry a bitmap of which 64 MiB blocks each holder will serve, so each block goes to a holder that already has it. A block no admitted holder covers is a genuine gap, warmed once from origin — and that warmer's new coverage becomes discoverable supply for the next fetch. -There is no new wire surface — each source is just an ordinary paid `cdn/client/v1` stream, so channels, vouchers, and slashing work exactly as in the single-source paths above. +Assignment is dynamic. When a source finishes, it steals the largest remaining range from a peer, splitting it on a verifiable boundary; every range is verified as it lands, and a stalled or failed source's _unfinished remainder_ is reassigned to another source without restarting the download. There is deliberately no hedging — racing a range against idle sources would mean paying for the copies that lose, so the tail is bounded by deadline-based reassignment instead. Aggregating parallel streams lets a blob be delivered at multi-gigabit speeds once enough holders cover it — no single origin's uplink caps the transfer. -## Payment channels in four lines +There is no new wire surface — each source is just an ordinary paid stream, so pools, vouchers, and slashing work exactly as in the single-source paths above. Every source draws on the client's one pool, on its own independent lane. -1. **Open** — client opens a channel on-chain. Deposit is escrowed in the `PaymentChannel` contract. -2. **Vouchers** — off-chain signed messages updating the cumulative amount owed. Cadence is negotiable per stream. -3. **Close** — either party submits the latest voucher on-chain. A dispute window lets the counterparty submit a later-nonce voucher if the close is stale. -4. **Cooperative close (fast path)** — when both parties are online and agree, a single co-signed transaction settles the channel immediately with no dispute window, falling back to the standard close if either side declines. +## Payments in four lines + +1. **Open** — the payer escrows one USDC deposit in the `PaymentPool` contract. Once, off the fetch path, reused for every node. +2. **Delegate** — the owner signs capped, expiring capabilities for whichever keys may spend from the pool. One capability is valid at every node. +3. **Meter** — a signed cumulative voucher anchors the amount owed, and a released hash-chain preimage advances it one megabyte at a time with no further signature. +4. **Redeem** — the node settles its own vouchers on-chain whenever it likes; the owner closes the pool to reclaim the remainder, after a grace window that lets nodes redeem first. ## What keeps the network honest -- **Hash verification.** Clients verify every chunk against the known blob hash. A bad byte voids payment. -- **On-chain evidence.** Protocol messages are signed in a form that can be verified on-chain as evidence of phantom announcements, rate manipulation, or blacklist violations ([slashing](/protocol/slashing)). -- **Synchronous adjudication.** The on-chain judge verifies submitted evidence and slashes on the spot, with no counter-evidence window. -- **Reputation.** A 0.0–1.0 score combining direct experience and signed gossip from staked nodes ([reputation](/protocol/reputation)). +- **Hash verification.** Clients verify every chunk group against the known blob hash as it arrives. A bad byte voids payment for that window. +- **On-chain evidence.** Probe and stream responses are signed in a form verifiable on-chain, as evidence of rate manipulation or blacklist violations ([slashing](/protocol/slashing)). +- **Synchronous adjudication.** The on-chain judge verifies revealed evidence and slashes on the spot, with no counter-evidence window. Challenges commit before they reveal, so the challenger reward cannot be front-run. +- **Reputation.** A local 0.0–1.0 score, built from a node's own delivery outcomes and nothing else ([reputation](/protocol/reputation)). - **Content blacklist.** Governance-managed on-chain hash blacklist. Serving a blacklisted hash after the compliance window is slashable ([takedown](/protocol/takedown)). ## Where the boundary sits @@ -76,9 +79,10 @@ There is no new wire surface — each source is just an ordinary paid `cdn/clien | Layer | Trust | | -------------------------- | ------------------------------------------------------------- | | Bytes delivered | **Verified** against known hash | -| Node availability | Trusted — slashed if phantom-announced | -| Node rate honesty | Trusted — slashed if advertised rate contradicts charged rate | +| Node availability | Not trusted — a node may decline; the client re-routes | +| Node rate honesty | Trusted — slashed if the charged rate contradicts its quote | +| Node region claim | Self-attested — penalized in selection when latency disagrees | | Origin backend correctness | **Assumed** — outside protocol scope (operator concern) | -| L2 RPC provider honesty | **Assumed** — mitigated via multi-source bootstrap | +| L2 RPC provider honesty | **Assumed** — mitigated via multiple independent providers | See [privacy](/protocol/privacy) for the adversary model. diff --git a/docs/overview/introduction.mdx b/docs/overview/introduction.mdx index cda3cec..913d7f9 100644 --- a/docs/overview/introduction.mdx +++ b/docs/overview/introduction.mdx @@ -3,15 +3,15 @@ title: Introduction description: deCDN is a decentralized CDN — staked independent nodes cache content and deliver it over peer-to-peer QUIC, paid per megabyte in USDC. --- -deCDN is a **decentralized content delivery network**. Instead of a single provider operating all edge servers, staked independent **nodes** cache content and deliver it to clients over peer-to-peer QUIC connections. Clients pay nodes directly, per megabyte, over off-chain payment channels. +deCDN is a **decentralized content delivery network**. Instead of a single provider operating all edge servers, staked independent **nodes** cache content and deliver it to clients over peer-to-peer QUIC connections. Clients pay nodes directly, per megabyte, with off-chain vouchers backed by a single on-chain deposit. ## Core properties - **Content-addressed.** Every blob is identified by its BLAKE3 hash. Clients verify the bytes they receive — they do not trust any node to return the right content. -- **Paid per megabyte.** Every byte delivered is paid, including node-to-node cache-miss pulls. Payments run on off-chain channels denominated in USDC (fixed at deployment), with on-chain settlement only at channel open and close. +- **Paid per megabyte.** Every byte delivered is paid, including node-to-node cache-miss pulls. Payments are off-chain vouchers denominated in USDC (fixed at deployment), backed by one shared on-chain pool that a payer opens once and reuses for every node it buys from. - **Origin-hidden.** Origin backends (S3, R2, B2, NFS, local disk) are opaque per-node configuration. No external origin URL is ever exposed to the network. - **Bonded and slashable.** Nodes post a TOKEN capacity bond (scaled to declared bandwidth) to join the peer mesh. Slashable offenses have on-chain evidence paths that anyone can challenge. -- **No single operator.** Gossip for discovery. A DHT for content location. Optimistic challenge-response for disputes. Operator governance weighted by delivered bytes, with a timelock for parameter updates. +- **No single operator.** The on-chain registry for node discovery. A DHT for content location. Signed protocol messages as on-chain slash evidence. Operator governance weighted by delivered bytes, with a timelock for parameter updates. ## What it isn't diff --git a/docs/overview/participants.mdx b/docs/overview/participants.mdx index ae9f684..28d30a9 100644 --- a/docs/overview/participants.mdx +++ b/docs/overview/participants.mdx @@ -11,7 +11,7 @@ A **node** is a bonded QUIC endpoint that caches and serves content-addressed bl - **Identity.** A network keypair bound on-chain to an Ethereum address at registration ([network](/protocol/network)). - **Bond.** A capacity bond in TOKEN, scaled to declared bandwidth (`bond = k × Mbps^α`) — the only TOKEN-side requirement to join the mesh; the bond scales with declared capacity rather than a flat minimum. -- **Gossip.** Publishes node metadata on a global topic and a regional topic. Also emits rate-change updates. +- **Discovery.** Registered on-chain with its addresses and region; publishes a DHT record for each blob it caches, and quotes its current rate in every signed probe response. - **Delivery.** Serves the paid-delivery protocol — the sole rail for both client→node and node→node traffic. - **Earnings.** Per-MB voucher revenue in USDC. @@ -29,15 +29,15 @@ Two deployment shapes: A **client** is a lightweight QUIC endpoint that consumes content and pays for it. - **Identity.** Ephemeral network keypair — no stake, no on-chain registration. -- **Gossip.** Subscribes to topics but does not publish. -- **Payment.** Opens an on-chain USDC payment channel with each node it buys from, then signs off-chain vouchers as bytes flow ([payments](/protocol/payments)). -- **Trust model.** Verifies hashes on all bytes received. Trusts nodes for availability (slashable if claimed and not delivered), not for content. +- **Discovery.** Reads the on-chain registry for its peer set and queries the DHT for holders. Publishes nothing and holds no network role. +- **Payment.** Opens one on-chain USDC pool, reuses it for every node it buys from, and signs cumulative off-chain vouchers as bytes flow ([payments](/protocol/payments)). +- **Trust model.** Verifies hashes on all bytes received. Trusts no node for content, and none for availability either — a node may decline to serve, and the client simply re-routes without paying for the attempt. -Clients can run a multi-node parallel download for large files and resume crashed transfers from the last verified byte. +Clients can run a multi-source parallel download for large files, pulling disjoint ranges from several holders at once, and resume crashed transfers from the last verified byte. ## External components -These are **not CDN protocol participants**. They ride the same QUIC transport but do not gossip, probe, or bond. +These are **not CDN protocol participants**. They ride the same QUIC transport but do not register, probe, or bond. ### Origin backend diff --git a/docs/protocol/content-addressing.mdx b/docs/protocol/content-addressing.mdx index 5914ecd..31b5e92 100644 --- a/docs/protocol/content-addressing.mdx +++ b/docs/protocol/content-addressing.mdx @@ -1,6 +1,6 @@ --- title: Content addressing -description: Every blob is addressed by its BLAKE3 hash and verified in-stream with bao, so clients never trust a node to return the right bytes. +description: Every blob is addressed by its BLAKE3 hash and verified in-stream with bao, with a namespace naming who is authorized to serve it from origin. --- ## Decision @@ -14,32 +14,38 @@ Every blob is identified by its **BLAKE3 hash**. Clients and nodes verify receiv ## Verified streaming -Delivery uses **bao** — an interleaved verified-stream encoding of the BLAKE3 tree. The `cdn/client/v1` paid-delivery payload is always bao bytes: content interleaved with the hash proofs needed to verify each 16 KiB chunk group against the root the client expects. The bao decoder verifies every chunk group as it arrives. A malicious node cannot insert a single corrupt byte and still earn — the client rejects the stream before paying for that chunk group. +Delivery uses **bao** — an interleaved verified-stream encoding of the BLAKE3 tree. The paid-delivery payload is always bao bytes: content interleaved with the hash proofs needed to verify each 16 KiB chunk group against the root the client expects. The bao decoder verifies every chunk group as it arrives. A malicious node cannot insert a single corrupt byte and still earn — the client rejects the stream before paying for that chunk group. Because every chunk group carries its own proof, a resumed or ranged fetch verifies **independently** — the decoder aligns the requested offset down to its 16 KiB chunk-group boundary, so there's no need to re-read from the start. Payment meters all transmitted bytes, including the proof bytes: the verification overhead is roughly **0.4%** of blob size, not free metadata. An origin-cold node must import or build the blob's **outboard** (its BLAKE3 tree) before it can serve verified ranges. Node-to-node cache-miss pulls stay bao-encoded and pipelined end to end. -## Hash-to-object-key mapping +## Publishers and namespaces + +A hash answers "what is this blob?". It does not answer "who is responsible for serving it?" — that is a **namespace**, a publisher-owned identifier for a content set and the unit of origin addressing. A delivery request pairs the hash with the namespace its content is published under, and the node routes on it: -Origin backends speak their own namespace: S3 objects are addressed by key, NFS by path, local disk by filename. Blobs are addressed by BLAKE3 hash. The mapping lives in a **content catalog** — a small database (PostgreSQL or SQLite) maintained by the operator: +| Namespace | How the node resolves a miss | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| Named (non-zero) | Routes to that namespace's authorized origins. If none hold the bytes, the fetch fails. | +| Default (zero) | No publisher, no authorized origins — served best-effort from cache or DHT-discovered holders, with no availability guarantee. | -```text -catalog: hash → { s3_bucket, s3_key, size_bytes, content_type } -``` +The namespace is a **routing hint, not a trust anchor**: bytes are verified against the hash independently, so a wrong or hostile namespace can only cause a failed fetch, never corrupt or mis-attributed delivery. Cache-only serving stays permissionless — any staked operator may re-serve bytes it holds, for any hash, under any namespace. Only the origin role is namespace-gated ([takedown](/protocol/takedown#origin-assignment)). + +## Hash-to-object-key mapping -The catalog is not on-chain; it is an operational concern. Origin-backed nodes query it on cache miss to locate the pull URL. +Origin backends speak their own namespace: S3 objects are addressed by key, NFS by path, local disk by filename. Blobs are addressed by BLAKE3 hash. The mapping between them is a private catalog held by the operator and queried on cache miss to locate the bytes. It is not on-chain and not part of the protocol — the protocol only requires that a node deliver the correct bytes for a given hash. ## Why the origin is hidden -Leaking the origin URL would bypass the pay-per-byte economic model — anyone could download directly from S3 and skip the CDN entirely. Redirects point to other nodes, never to an external URL. The origin backend is not addressable from outside the node process. +Leaking the origin URL would bypass the pay-per-byte economic model — anyone could download directly from S3 and skip the CDN entirely. A stream response carries no route hint of any kind, so the backend topology of origin-backed nodes is fully hidden from the network, and the origin backend is not addressable from outside the node process. This also protects origin operators from direct egress cost attacks: an adversary cannot generate S3 traffic by pointing clients at the bucket URL. ## What clients verify vs. trust -| Property | Verified | Trusted | -| ----------------- | ---------------- | -------------------------------------------------------------------------- | -| Byte correctness | BLAKE3 hash tree | — | -| Blob availability | — | Node's signed availability claim (slashable via phantom-announcement path) | -| Origin durability | — | Origin operator's S3/NFS configuration (off-protocol) | +| Property | Verified | Trusted | +| ----------------- | ---------------- | ------------------------------------------------------------------- | +| Byte correctness | BLAKE3 hash tree | — | +| Blob availability | — | Node may decline to serve; the client re-routes and re-pays nothing | +| Rate honesty | — | Quoted rate is binding within the slashing window (slashable) | +| Origin durability | — | Origin operator's S3/NFS configuration (off-protocol) | diff --git a/docs/protocol/governance.mdx b/docs/protocol/governance.mdx index 6e5baa5..ee9cf37 100644 --- a/docs/protocol/governance.mdx +++ b/docs/protocol/governance.mdx @@ -5,7 +5,7 @@ description: Voting weight is bytes actually delivered, not TOKEN held — a 4% ## Decision -OpenZeppelin Governor whose voting weight is an operator's **delivered bytes over a trailing window** — not TOKEN balance — with a **4% quorum** and a **2-day timelock**. All governable parameters have hardcoded safety bounds that governance cannot override. A **3-of-5 emergency multisig** can pause contracts, add emergency blacklist entries, and triage disputed slashes and blacklist entries. Its sunset is **split by capability**: the protocol-wide `pause()` brake has a **12-month sunset** enforced via an immutable constructor deadline — governance cannot extend it; prolonging `pause()` past the deadline requires deploying a new contract version and migrating through the standard upgrade path. The narrow unlawful-content-removal power — emergency blacklisting — is **permanent**, discharging an ongoing legal-compliance duty the ~11-day governance takedown path cannot meet. +OpenZeppelin Governor whose voting weight is an operator's **delivered bytes over a trailing window** — not TOKEN balance — with a **4% quorum** and a **2-day timelock**. All governable parameters have hardcoded safety bounds that governance cannot override. A **3-of-5 emergency multisig** can pause contracts, add emergency blacklist entries, decide slash appeals, and suspend a compromised regional governance body. Its sunset is **split by capability**: the protocol-wide `pause()` brake has a **12-month sunset** enforced via an immutable constructor deadline — governance cannot extend it; prolonging `pause()` past the deadline requires deploying a new contract version and migrating through the standard upgrade path. The narrow unlawful-content-removal power — emergency blacklisting — is **permanent**, discharging an ongoing legal-compliance duty the ~11-day governance takedown path cannot meet. ## Voting weight @@ -15,23 +15,30 @@ Voting weight is each operator's **served bytes over a trailing window** (defaul - **Fresh operators ramp in.** A new operator that serves heavily on day one still votes at a fraction of its byte share until the tenure ramp completes over several months. - **Slashing zeroes out the vote.** A slashed operator's weight drops to zero for the rest of the window, then recovers as the window slides past the slash. -Bytes are read from the on-chain `FeeRouter` settlement counter, so weight is auditable and cannot be claimed without paid delivery. +Bytes are read from the on-chain `FeeRouter` settlement counter, so weight is auditable and cannot be claimed without paid delivery. Voting power is delegable by signed message; the bond itself is not. ### Launch phase -Served-bytes DAO voting only activates once the operator set is large enough to make it safe. Until then — the first 6–12 months post-launch — a **5-of-9 bootstrap multisig** (distinct from the emergency multisig) operates the protocol within the same safety bounds. The bootstrap phase ends, irreversibly, when the active operator count reaches **≥ 30** _and_ total declared capacity reaches **≥ 100 Gbps**; at the transition, governance role authority transfers to the timelock-controlled Governor and the bootstrap multisig cannot be reinstated. +Served-bytes DAO voting only activates once the operator set is large enough to make it safe. Until then — the first 6–12 months post-launch — a **5-of-9 bootstrap multisig** (distinct from the emergency multisig) operates the protocol within the same safety bounds. The multisig never holds governance authority directly: the timelock holds it from deployment onward, so the 2-day delay applies to every parameter change regardless of who requested it. What the bootstrap phase confines is the right to _schedule_. + +The phase ends when the multisig judges the operator set broad and diverse enough — a manual decision, not a threshold that trips on its own. It schedules a single timelock batch granting the Governor the right to propose and cancel, then revoking its own. Executing that batch strips the multisig's ability to schedule anything, including a proposal restoring itself, so the transition is irreversible: only the inheriting DAO could hand the role back, by its own vote. ## Hardcoded safety bounds Even governance cannot set parameters outside these bounds: -| Parameter | Bounds | -| ----------------------------------------- | ----------- | -| Operator revenue share (`FeeRouter` base) | 40% – 90% | -| Burn share (`FeeRouter`) | ≥ 5% | -| Dispute window | 12 h – 72 h | -| Per-operator voting cap | 1% – 25% | -| Unbonding window | 7 – 60 days | +| Parameter | Default | Bounds | +| ----------------------------------------- | --------- | ------------- | +| Operator revenue share (`FeeRouter` base) | 60% | 40% – 90% | +| Burn share (`FeeRouter`) | 30% | 5% – 50% | +| Treasury share (`FeeRouter`) | 10% | 0% – 30% | +| Redemption grace window | 48 h | 48 h – 72 h | +| Per-operator voting cap | 5% | 1% – 25% | +| Trailing vote window | 13 epochs | 4 – 26 epochs | +| Capacity-curve exponent (α) | 1.2 | 1.0 – 1.8 | +| Unbonding window | 14 days | 7 – 60 days | + +The three `FeeRouter` shares must sum to exactly 100% on every update. The burn share is the one that also accepts **zero** — the dormant launch configuration, with the split at 90/0/10 until governance activates the buyback sink toward the 60/30/10 steady state. It is therefore either off or at 5% or more; governance cannot leave it nominally on at a negligible share. Bounds are enforced in `require()` checks in the setter functions. A malicious governance proposal that attempts to push the operator revenue share below its floor simply reverts. (Slashing tiers — 5% / 15% / 50% by lifetime offense count — are fixed constants, not governable parameters; see [slashing](/protocol/slashing).) @@ -41,7 +48,8 @@ Bounds are enforced in `require()` checks in the setter functions. A malicious g - **Pause** any contract (`Pausable`) - **Add emergency blacklist entries** (with 14-day auto-expiry or 90-day for CSAM/terrorist categories — see [takedown](/protocol/takedown)) -- **Triage disputed slashes and blacklist entries** — fast-track or reject an appeal within a fixed window, never decide it unilaterally +- **Decide slash appeals** — fast-track or reject an open appeal; the escrow movement itself is performed deterministically by the bond contract +- **Suspend a regional governance body** — subject to governance ratifying or reversing within 14 days Can **not**: diff --git a/docs/protocol/network.mdx b/docs/protocol/network.mdx index 12f5c1d..e9e1a3e 100644 --- a/docs/protocol/network.mdx +++ b/docs/protocol/network.mdx @@ -1,25 +1,37 @@ --- title: Network topology -description: A flat mesh of staked nodes — gossip on regional and global topics for node discovery, a Kademlia-subset DHT for content, and on-chain registration. +description: A flat mesh of staked nodes — the on-chain registry for node discovery, a Kademlia-subset DHT for content, and probes to confirm availability before paying. --- ## Decision -All staked nodes form a **flat mesh**. Node metadata propagates over a gossip overlay on regional and global topics. Content discovery uses a Kademlia-subset DHT, with broadcast probe fan-out as bootstrap/emergency fallback. +All staked nodes form a **flat mesh** with no routing hierarchy. Node discovery reads the on-chain registry's active set. Content discovery uses a Kademlia-subset DHT, with the on-chain origin directory as the deterministic last-resort fallback. A probe confirms live availability and measures latency before any paid pull. + +## Node discovery + +The on-chain registry's active set is the sole membership source. A node reads it at startup and keeps it fresh by subscribing to registration, address-update, deregistration and auto-ejection events; sub-second L2 block times keep the staleness window small. Clients do the same and re-read periodically, falling back to a cached peer list — with its age surfaced — when the RPC endpoint is unreachable. + +The same active set seeds the DHT routing table, so a freshly started node participates in lookups immediately. A node dials any peer by its network identity, which resolves to reachable transport addresses on its own — there is no separate address-exchange layer, and no metadata broadcast. + +## Content discovery + +A cache miss issues a DHT lookup for the hash, which returns candidate holders along with a coarse **coverage bitmap** per holder — so a requester can prune candidates that do not hold the range it wants. Candidates are then probed in parallel to confirm live availability, fresh coverage, latency and price. Collection stops as soon as enough blob-holding candidates answer to fill the failover budget, and otherwise at a 500 ms ceiling that accommodates inter-continental round trips. + +When a lookup returns no providers, the on-chain origin directory is the deterministic fallback. If that returns nothing either, the blob is not available in the network. ## Selection -Clients and nodes both rank candidates by a unified score combining advertised price, observed latency, and reputation. Lower is better. The exact weighting is part of the protocol implementation. +Clients and nodes both rank candidates by a unified score combining advertised price, observed latency, and reputation, with a quadratic reputation penalty. Lower is better. ## On-chain registration Nodes register through an on-chain staking registry that binds a network identity to an Ethereum address. Registration requires proof of control of both keys — preventing identity squatting and ensuring every staked node is slashable. Registration also carries acceptance of the DAO-canonical operator terms hash, signed into the registration message and enforced on-chain; the terms hash is governance-swappable, so the accepted version is recorded at registration. Key rotation remains available post-registration. -## Regional topics +## Regions -Nodes subscribe to a region topic plus the global topic. Regional topics reduce gossip noise for latency-sensitive selection: a client in Frankfurt typically prefers DE/NL/AT candidates over US ones, and the regional topic surfaces those preferentially. +A node declares its region — an ISO 3166-1 alpha-2 country code — on-chain at registration, and the registry watcher resolves the active set into a region map. The claim is self-attested and accepted at face value; a requester applies a reputation penalty when observed latency contradicts it, and region-scoped takedown obligations follow it ([takedown](/protocol/takedown)). A stability window after a region change stops a node shedding blacklist scope by flipping regions reactively. -Clients subscribe but do not publish. +Regions inform selection rather than partition the mesh: a client in Frankfurt prefers nearby candidates because they score better on latency, not because it sees a different peer set. ## NAT traversal diff --git a/docs/protocol/payments.mdx b/docs/protocol/payments.mdx index a8f7f4f..b6e6837 100644 --- a/docs/protocol/payments.mdx +++ b/docs/protocol/payments.mdx @@ -1,45 +1,74 @@ --- title: Payments -description: Clients pay nodes per MB over off-chain USDC payment channels with on-chain settlement, at market-driven rates inside governance-set bounds. +description: Clients pay nodes per MB with off-chain vouchers backed by one shared on-chain USDC pool, metered by a hash chain and redeemed per node. --- ## Decision -Clients pay nodes **per MB** over off-chain **payment channels** settled on-chain. All channels are denominated in **USDC**, fixed at contract deployment. On a cache miss, nodes pay peers per MB for initial content pulls, then amortize that cost across many client deliveries. Origin-backed nodes set the effective price ceiling (their backend egress costs). Rates are fully market-driven within governance-set bounds. +Clients pay nodes **per MB** with off-chain **vouchers backed by a shared on-chain pool**. All pools are denominated in **USDC**, fixed at contract deployment. On a cache miss, nodes pay peers per MB for content pulls, then amortize that cost across many client deliveries. Origin-backed nodes set the effective price ceiling (their backend egress costs). Rates are fully market-driven within governance-set bounds. -## Channel lifecycle +One funded pool backs payments from **many capped signers** to **many nodes**. There is no channel per node and no channel per client: the owner opens a pool once, reuses it, and closes it to reclaim the unspent remainder. + +## Pool lifecycle ```mermaid sequenceDiagram - participant C as Client + participant C as Client (pool owner) participant N as Node - participant CH as PaymentChannel (on-chain) + participant P as PaymentPool (on-chain) - C->>CH: openChannel(provider, deposit) + C->>P: openPool(deposit) + Note over C,N: Capability signed off-chain:
{signer, spending_cap, pool_id, expiry} loop Per stream - C->>N: Cumulative voucher (off-chain, signed, nonce++) + C->>N: Voucher (off-chain, signed, cumulative amount) + C->>N: Preimage per 1 MiB chunk delivered end - N->>CH: closeChannel(latest voucher) - Note over CH: Dispute window — either party
can submit a higher-nonce voucher
via disputeChannel - CH->>CH: settleChannel - CH-->>N: Settle → provider - CH-->>C: Refund remaining deposit + N->>P: redeem(voucher, preimage) + P-->>N: Settle → provider (via FeeRouter) + C->>P: closePool + Note over P: Redemption grace window
lets nodes redeem first + P-->>C: Reclaim unspent remainder ``` -The channel token is fixed (USDC) at deployment, so `openChannel` takes only the provider and deposit. Close is a three-step lifecycle — `closeChannel` (callable by either party) starts the dispute window, `disputeChannel` accepts a higher-nonce voucher during the window, and `settleChannel` distributes funds after it expires. A node may also `withdraw` accrued earnings against the latest voucher while the channel stays open — redeeming a monotonic client-signed claim needs no dispute window. +`openPool` happens **once and off the fetch path**, so it is not latency-critical; `topUp` refills a pool that has been drawn down. A node redeems whenever it likes while the pool is open — redeeming a signed, monotone claim needs no dispute window. The owner closes the pool to reclaim what is left, after a **redemption grace window** (48 hours by default, governable within 48h–72h) during which nodes settle outstanding vouchers. + +## Capabilities -## Cooperative close +The pool owner authorizes each voucher signer with an off-chain **capability** signed over `{signer, spending_cap, pool_id, expiry}`. It is **node-agnostic** — one capability is valid at every node — and it is registered on-chain once, on that signer's first redemption. -When both parties are online and agree on the final balance, they can skip the dispute window entirely. `cooperativeClose(channelId, amount, nonce, bytesDelivered, clientVoucherSig, providerCloseSig)` settles the channel **in a single transaction with no dispute window** — the provider signs an EIP-712 `CooperativeClose` waiver acknowledging the final voucher, and either party can submit it. This is purely additive: it is the fast path when both sides cooperate, and if the node declines or is offline, the client falls straight back to `closeChannel` and the standard dispute window. The client CLI exposes it as `decdn channel coop-close`. +Delegation bounds the damage a compromised key does: `spending_cap` bounds what the key can authorize, `expiry` retires it if the owner doesn't renew. The owner keeps sole control of top-up and close. A single user makes its own key the sole signer; a client fanning out to per-device or per-session keys issues each a small capped capability from one pool. + +The sum of the caps may exceed the deposit. Nodes keep a pool solvent by reserving a refundable minimum remaining balance and stopping service before the pool reaches it. ## Vouchers -Off-chain signed messages bound to a specific channel, carrying a strictly increasing **nonce** (starting at 1) and a **cumulative** amount and byte count. Each new voucher restates the running total owed — both amount and bytes are monotonically non-decreasing — and the highest-nonce voucher is the one that settles. Nodes persist the latest nonce rather than discarding older state, since `disputeChannel` only accepts a strictly higher nonce. Cadence is negotiated per stream. +Off-chain signed messages over `{pool_id, signer, provider, amount, bytes_delivered, chain_root, chunk_price}`. Two properties matter: + +- **Node-addressed.** The voucher names its payee, so one node cannot redeem a voucher meant for another and each node keeps its own independent ordering. +- **Cumulative.** `amount` is a settlement anchor that only moves up. A re-submitted voucher pays zero; there is no nonce and no dispute window. + +## Hash-chain metering + +Between signatures, delivery is metered by a **hash chain** rather than a new signature per interval. The payer commits `chain_root = keccak^255(seed)` in the voucher, and after verifying chunk `k` releases `keccak^(255−k)(seed)`. The node hashes that preimage forward until it reaches a value it already trusts. + +Redemption resolves both objects with one formula: + +```text +claimed = amount + chain_index × chunk_price +``` + +A preimage is self-proving — nobody derives a deeper one without the seed — so accepting a tick costs one hash, with no signature, no round trip and nothing to persist before the next chunk ships. Signatures are therefore O(1) per transfer rather than one per interval. + +The payment quantum is `chunk_bytes` = **1 MiB**, a protocol constant. Nothing negotiates it and no message carries it: the payer and node must price a released preimage identically, and a fixed constant removes the disagreement rather than negotiating it away. Because a chunk is exactly one MB of price, `chunk_price` is the node's advertised `rate_per_mb` — and the node rejects any voucher whose signed `chunk_price` does not equal its own quote. + +## Credit window + +A node's exposure is bounded by a per-lane **credit window** — the unpaid balance it will carry before it pauses — not by the meter's resolution. The window starts at a floor and ramps as the lane pays, so a new payer proves itself on small credit before a node extends more. ## Rate discovery -Each node advertises its own per-MB rate. Governance sets rate bounds (in USDC); a node advertising outside the bounds is ignored by selection. Rates can change between streams. +Each node advertises its own per-MB rate, quoted in every signed probe response. Governance sets rate bounds (in USDC); a node advertising outside the bounds is ignored by selection. A quoted rate is binding for any stream opened inside the slashing window ([slashing](/protocol/slashing)). -## Dispute window +## Minimum deposit -A window after `closeChannel` during which either party can submit a later (higher-nonce) voucher via `disputeChannel` if a stale close was filed. Default 48 hours, governable within 12h–72h. +`openPool` enforces a governance-set `minDeposit` on the credited deposit. It is a Sybil knob: the per-pool solvency floor bounds loss within one pool, and an attacker escapes it by fanning out across many pools, so the floor taxes the capital locked in simultaneous pools. Deposits stay fully refundable, and the parameter **ships dormant at zero** so small honest pools remain available; governance arms it if Sybil pressure warrants. diff --git a/docs/protocol/privacy.mdx b/docs/protocol/privacy.mdx index d5c26e8..61175e6 100644 --- a/docs/protocol/privacy.mdx +++ b/docs/protocol/privacy.mdx @@ -1,18 +1,18 @@ --- title: Privacy analysis -description: An accountability-first privacy design — a four-tier adversary model, an inventory of privacy surfaces, and the four specified mitigations. +description: An accountability-first privacy design — a four-tier adversary model, an inventory of privacy surfaces, and the two mitigations committed pre-mainnet. --- ## Decision -Privacy is treated as an **accountability-first** design: probes are public, on-chain channels enable disputes, gossip enables discovery. Most privacy surfaces are accepted as inherent. Four concrete mitigations are specified below; deeper techniques (dummy probes, channel mixing) are future work. +Privacy is treated as an **accountability-first** design: probes are public, on-chain settlement is what makes payment auditable, and the registry is what makes nodes discoverable and slashable. Most privacy surfaces are accepted as inherent. Two mitigations are committed pre-mainnet; deeper techniques are assessed and deferred. ## Adversary model (four tiers) | Tier | Example adversary | What they can observe | | --------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------- | | **Passive observer** | On-path network middlebox | QUIC metadata (IPs, SNI unless ECH is deployed), timing, volume | -| **Active participant** | Malicious node or client | Probes, gossip activity, payment channel open/close | +| **Active participant** | Malicious node or client | Probes, stream requests, payment pool opens and redemptions | | **Infrastructure operator** | L2 RPC provider, relay host | On-chain events at read time; relay endpoints see encrypted QUIC flows | | **Compromised endpoint** | Malicious origin or content-provider infrastructure | Plaintext bytes, client subscription identity | @@ -20,21 +20,23 @@ Privacy is treated as an **accountability-first** design: probes are public, on- The protocol inventories its distinct privacy surfaces with explicit dispositions (accept or mitigate). Highlights: -- **On-chain settlement volume leakage.** Anyone can read channel open/close amounts on-chain. Accepted: disputes require public channel state. +- **On-chain settlement volume leakage.** Anyone can read pool deposits and per-node redemptions on-chain. Accepted: auditable settlement is what makes the payment layer trustless. - **Signed protocol messages as non-repudiable content commitments.** A node that signs an availability response has provably committed to serving that hash. Accepted: the same signature is what enables on-chain accountability. - **Probe traffic.** Who probes what is visible to the probed node. Accepted: selection depends on probing; fully private probing would require PIR at O(log N) blow-up. +- **Registry enumeration.** Anyone can read the active set and each node's self-reported region and addresses. Accepted: an enumerable, slashable operator set is the point. ## Mitigations -1. **Client NodeId rotation.** Clients rotate NodeIds on session boundaries to break linkability across subscription sessions. -2. **Bounded availability hints.** Probe responses include only a small number of additional cached hashes, lowering the precision of per-node content fingerprinting. -3. **Client key encryption via platform keychain.** Clients store Ethereum keys in macOS Keychain, Windows Credential Manager, or Gnome Keyring — not plaintext files. -4. **Multi-source RPC.** Clients and nodes use multiple independent RPC providers to reduce single-vendor deanonymization surface. +Both are pre-mainnet commitments: -## Future work +1. **Client key encryption via platform keychain.** Clients store Ethereum keys in macOS Keychain, Windows Credential Manager, or Gnome Keyring — not plaintext files. +2. **Multi-source RPC.** Clients and nodes use multiple independent RPC providers to reduce single-vendor deanonymization surface. -- **Dummy probes.** Inject cover-traffic probes to obscure real content interest. Unresolved cost/benefit. -- **Payment channel mixing.** Route settlements through a mixing contract. Regulatory complexity. +## Assessed and not adopted + +- **Client identity rotation.** Rotating the transport identity between sessions is a supported capability, but it is not a privacy commitment: every request carries the pool identifier, and a serving node must resolve the signer's address to be paid at all. That stable payment identity is what an observer correlates on, regardless of node count — so rotation obscures only non-serving probers, whose view is already accepted above. +- **Dummy probes.** Cover-traffic probes hide requests only from nodes that were never going to deliver — the delivering node must learn the hash regardless. Deferred post-mainnet: 3–5× the probe traffic for marginal benefit. +- **Payment pool mixing.** Breaking the on-chain payer↔provider link needs regulatory analysis before any design work. Deferred post-mainnet; pools are long-lived and amortized across many sessions, so they reveal less than per-transfer settlement would. ## Scope clarifications diff --git a/docs/protocol/reputation.mdx b/docs/protocol/reputation.mdx index b872012..718282f 100644 --- a/docs/protocol/reputation.mdx +++ b/docs/protocol/reputation.mdx @@ -1,35 +1,48 @@ --- title: Reputation -description: Nodes are scored 0.0–1.0 from local delivery outcomes plus weighted gossip from staked peers, with per-report clamps, decay, and a cold-start bonus. +description: Each node scores its peers 0.0–1.0 from its own delivery outcomes alone — an EWMA over speed, correctness and reachability, decaying toward neutral. --- ## Decision -Nodes are ranked by a **reputation score** in `[0.0, 1.0]` derived from a combination of direct local observation and signed gossip reports from staked nodes. Local experience dominates; gossip sharpens the picture for nodes you have not yet interacted with. +Reputation is **local-only**. Each node scores its peers from its own direct delivery observations and nothing else: no propagation, no cross-node aggregation, no network-wide score, no on-chain reputation state. A node's ranking of a peer reflects only interactions it witnessed itself — the same tit-for-tat principle BitTorrent uses for peer selection, which needs no global reputation to work at scale. -Only staked nodes may submit gossip reports. Clients contribute to local scores only. +Reputation is **not** the Sybil or corruption defense — stake, the capacity bond, mandatory progressive BLAKE3 verification and no-payment-on-failed-delivery are. It is not an eligibility gate, a governance input, or a component of settlement or vote weight. It only tunes each requester's selection probability among otherwise-eligible nodes. -## Reporter weighting +## Scoring -A report's weight is a function of the reporter's track record and counterparty diversity, computed against on-chain settlement history. A node that has only ever settled with itself and a few colluders is heavily down-weighted; a node that has settled fairly with many counterparties is trusted more. Reporter weight is bounded so no single party dominates. +Scores live in `[0.0, 1.0]`. A peer a node has never used is **unscored and treated as neutral (0.5)**, so it stays fully selectable — traffic keeps exploring rather than converging on incumbents. -## Clamping and cold-start +After each interaction the node folds the outcome in with an EWMA (α = 0.1): -- **Per-report clamp.** A single report can move a target's score by at most a bounded amount, preventing flash-crashes from coordinated negatives. -- **Decay toward neutral.** In the absence of fresh reports, scores drift toward a neutral value over time. -- **Cold-start bootstrap.** New nodes get a small initial reputation bonus so they can earn their first traffic. The bootstrap is one-time per operator address — re-staking under a new identity does not replay it. +```text +local_score = ewma(local_score, interaction_score, alpha = 0.1) +interaction_score = 0.4 × speed + 0.4 × correctness + 0.2 × reachability +``` -## What triggers reports +| Signal | Range | Weight | +| ------------------------------------------ | ------------ | ------ | +| Delivery speed (bytes/sec vs. a reference) | 0.0–1.0, log | 40% | +| Data correctness (BLAKE3 verified) | 0.0 or 1.0 | 40% | +| Connection success (reachable?) | 0.0 or 1.0 | 20% | -Interaction outcomes generate reports. Examples: +Speed is normalized on a log curve against a node-local reference throughput (default 1 GiB/s) that deliberately **does not saturate** — a materially faster peer earns a materially better score instead of tying with every other fast peer. -| Event | Local score change | Gossip report? | -| -------------------------------------------- | ------------------ | ------------------------------------------------- | -| Clean delivery, valid voucher accepted | Small positive | Optional | -| Hash mismatch mid-stream | Large negative | Yes | -| Probe claimed availability but stream failed | Negative | Yes (candidate for phantom-announcement slashing) | -| Charged rate ≠ advertised rate | Negative | Yes (candidate for rate-manipulation slashing) | +## Why correctness is only one signal among several -## Why not purely on-chain? +Reputation measures service quality, not honesty. Corruption is absorbed at the wire: progressive BLAKE3 verification means a corrupt window yields no voucher, so the node ships garbage unpaid. The reputation hit compounds on top — a corruption event zeros the correctness component, drags the EWMA down, and degrades selection through a quadratic reputation penalty. Lost revenue plus lost traffic make sustained corruption irrational without an on-chain path. -On-chain reputation scales poorly — thousands of report transactions per hour would be cost-prohibitive. Reports stay in gossip and only escalate to on-chain slashing when the evidentiary bar is crossed. Reporter weighting still anchors to on-chain settlement history, so it remains auditable. +## Decay toward neutral + +Without fresh data a score decays back to 0.5, applied closed-form at read time from the last update: + +```text +score_new = 0.5 + (score_old − 0.5) × 0.5 ^ (elapsed / half_life) +``` + +`half_life` is node-local, default 3 days. Each half-life halves the distance to neutral, so a peer that stops being used drifts back to unopinionated rather than holding a stale high or low score. + +## What does not move a score + +- **Requester-side aborts.** A pull abandoned because throughput fell below the requester's floor produces no interaction outcome and never updates the EWMA. The slowness may come from the link, from congestion, or from the requester's own consumption, and a throughput signal is spoofable — so it is not evidence about the peer. The requester still stops using that peer for that blob, as a local, reputation-neutral suppression. +- **Anything a peer reported.** Scores never travel between nodes, so there are no third-party reports to weight, replay, or coordinate. diff --git a/docs/protocol/slashing.mdx b/docs/protocol/slashing.mdx index 34349a9..17187f4 100644 --- a/docs/protocol/slashing.mdx +++ b/docs/protocol/slashing.mdx @@ -1,17 +1,27 @@ --- title: Slashing & on-chain verification -description: Three slashable offenses — phantom announcement, rate manipulation, blacklist violation — adjudicated on-chain, with a 30-day operator appeal. +description: Two slashable offenses — rate manipulation and blacklist violation — adjudicated on-chain via commit–reveal, with a 30-day operator appeal. --- ## Decision -Three slashable offenses with concrete on-chain evidence paths: +Two slashable offenses, each resting on cryptographically dispositive signed evidence: -1. **Phantom announcement** — claimed availability, failed to deliver. -2. **Rate manipulation** — charged a rate different from the advertised rate. -3. **Blacklist violation** — served a blacklisted hash after the compliance window. +1. **Rate manipulation** — charged a rate different from the rate quoted in a signed probe response within the slashing window. +2. **Blacklist violation** — served a blacklisted hash after the compliance window. -A unified on-chain judge adjudicates all three synchronously and triggers the bond reduction immediately upon verification — there is no in-protocol counter-evidence window, because each offense rests on cryptographically dispositive signed evidence. The reduced bond is then held in escrow pending a post-slash appeal (below), not distributed on the spot. +Every probe and stream response carries a mandatory signature over its security-relevant fields, produced with the node's Ethereum key, so any protocol message is portable evidence verifiable on-chain. A unified on-chain judge adjudicates both offenses synchronously and triggers the bond reduction immediately upon verification — there is no in-protocol counter-evidence window. The reduced bond is then held in escrow pending a post-slash appeal (below), not distributed on the spot. + +## What is not slashable + +- **Corrupted delivery.** Absorbed at the wire: the client verifies each chunk group as it arrives and signs no voucher for a corrupt window, so the node ships garbage unpaid. There is no on-chain path. +- **Refusing to serve.** A node may decline any request — for price, capacity, or policy reasons. The client re-routes to another provider and pays only a latency cost. + +## Commit–reveal challenges + +Submitting evidence is a two-phase flow: a `commitChallenge` registers an opaque commitment that moves no funds, and the reveal resolves synchronously once the commitment matures. + +The phases exist because the reveal must disclose the full evidence — the signed responses themselves — in its calldata. Without a prior commitment, a mempool watcher could copy a pending honest challenge, resubmit it under its own address, and capture the challenger reward. The bond round-trips either way, so only the reward is at risk; but that reward is the entire incentive for off-path witnesses, so leaving it extractable would hollow out enforcement. ## Evidence staleness @@ -19,8 +29,8 @@ Evidence has a maximum age, set strictly below the unbonding period — so an ac ## Escrow and appeals -The bond reduction is immediate, but the slashed TOKEN sits in `CapacityBond` escrow rather than being paid out at slash time. The slashed **operator** — and only the operator — may file an **operational-failure appeal** within **30 days** (e.g. a genuine network outage, NTP drift, or hosting incident prevented compliance), posting a TOKEN appeal bond. The emergency multisig fast-tracks or rejects within a fixed window; the Governor then grants or upholds. A **granted** appeal refunds the full escrowed bond to the operator; a **failed** appeal distributes the escrow like an unappealed slash. The lifetime offense counter is unchanged either way — an appeal restitutes capital, not standing. +The bond reduction is immediate, but the slashed TOKEN sits in escrow rather than being paid out at slash time. The slashed **operator** — and only the operator — may file an **operational-failure appeal** within **30 days** (e.g. a genuine network outage, NTP drift, or hosting incident prevented compliance), posting a TOKEN appeal bond. The emergency multisig fast-tracks or rejects within a fixed window; the Governor then grants or upholds. A **granted** appeal refunds the full escrowed bond to the operator; a **failed** appeal burns the appeal bond and distributes the escrow like an unappealed slash. The lifetime offense counter is unchanged either way — an appeal restitutes capital, not standing. ## Challenger incentives -Submitting evidence requires a **100 TOKEN** bond. A challenge that fails on-chain verification simply reverts — the challenger only loses gas, and the bond is not transferred. A challenge that passes always slashes the node and the bond is returned. At finality (≤ 30 days with no appeal, later if one is filed) the escrowed bond is split **50% to the challenger / 50% burned**, unless the operator's appeal is granted, in which case the operator is refunded and no challenger reward is paid. +Submitting evidence requires a **100 TOKEN** bond, transferred at the reveal. A reveal that fails on-chain verification simply reverts — the challenger only loses gas, and the bond is not transferred. A challenge that passes always slashes the node and the bond is returned. At finality (≤ 30 days with no appeal, later if one is filed) the escrowed bond is split **50% to the challenger / 50% burned**, unless the operator's appeal is granted, in which case the operator is refunded and no challenger reward is paid. diff --git a/docs/protocol/takedown.mdx b/docs/protocol/takedown.mdx index 488232c..8cfe0db 100644 --- a/docs/protocol/takedown.mdx +++ b/docs/protocol/takedown.mdx @@ -1,11 +1,14 @@ --- title: Content takedown -description: An on-chain hash blacklist with global and regional entries, a 24-hour compliance window for governance takedowns, and an immediate emergency path. +description: Symmetric on-chain authority over origins — a hash blacklist with global, regional and emergency entries, plus publisher-seated origin assignment. --- ## Decision -A **`ContentBlacklist`** contract supports **global** (network-wide) and **regional** (jurisdiction-scoped) takedown. Standard governance entries have a **24-hour compliance window**; the emergency multisig path takes effect **immediately** with a **2-hour slash window**. +Content governance over origins has two symmetric authorities, both DAO-controlled: + +- **Negative — `ContentBlacklist`.** Removes hashes and operators through a **global** (network-wide) path and a **regional** (jurisdiction-scoped) path. Standard governance entries carry a **24-hour compliance window**; the emergency multisig path takes effect **immediately** with a **2-hour slash grace**. +- **Positive — `OriginAssignment`.** Authorizes specific operators to act as origins for specific namespaces ([content addressing](/protocol/content-addressing#publishers-and-namespaces)). The emergency takedown power is a **permanent** capability — unlike the multisig's protocol-wide `pause()`, which [sunsets at 12 months](/protocol/governance#emergency-multisig), emergency blacklisting does not sunset, since unlawful-content removal is an ongoing legal-compliance duty. (Individual emergency _entries_ still auto-expire unless ratified — see [Entry types](#entry-types) below.) @@ -17,7 +20,7 @@ The blacklist must be: - **Atomic per-node.** Every node polls the same source of truth. No partial propagation. - **Auditable.** Anyone can see what has been blacklisted and when. -Gossip-only blacklists fail all three. On-chain entries with governance gating meet them. +Off-chain distribution fails all three. On-chain entries with governance gating meet them. ## Entry types @@ -28,25 +31,42 @@ Gossip-only blacklists fail all three. On-chain entries with governance gating m | Emergency | 3-of-5 multisig | Immediately (with 2 h slash grace) | 14 days | | Emergency (CSAM / terrorist) | 3-of-5 multisig | Immediately | 90 days | -Emergency entries **auto-expire unless ratified by governance** before the deadline — preventing the multisig from maintaining a permanent de-facto blacklist without oversight. +Emergency entries **auto-expire unless ratified by governance** before the deadline — preventing the multisig from maintaining a permanent de-facto blacklist without oversight. Expiry needs no transaction: the entry simply stops being enforceable, and anyone may then clean it out of storage permissionlessly. + +The emergency path is deliberately one-way with respect to governance — it can always make enforcement stricter, never looser. Both compliance windows are governable within one set of hardcoded bounds: minimum 1 hour, maximum 7 days. The floor is what keeps the emergency path honest — governance cannot compress the window below a single poll cycle and slash nodes for entries they had no opportunity to learn about. ## Why the 2-hour slash grace -The emergency path takes effect immediately in terms of "don't serve this", but the first 2 hours cannot result in a slash. This absorbs gossip propagation lag and clock skew — a node that genuinely hasn't seen the entry yet is given a realistic reaction window before liability attaches. +The emergency path takes effect immediately in terms of "don't serve this", but the first 2 hours cannot result in a slash. This absorbs blacklist-poll lag and clock skew — a node that genuinely hasn't seen the entry yet is given a realistic reaction window before liability attaches. ## Regional scoping -A **regional** entry applies only to nodes that advertise a matching region. The right to add regional entries is granted to regional governance bodies on a per-region basis. +A **regional** entry applies only to nodes that advertise a matching region. The right to add regional entries is granted to regional governance bodies on a per-region basis; the emergency multisig can suspend a compromised body immediately, subject to governance ratifying or reversing the suspension within 14 days. Suspension bounds a body's future authority — entries it already issued stay live. + +A node serving a globally-blacklisted hash is always slashable. A node in `region: DE` serving a `DE`-regional entry is slashable; that same node serving the entry via a US client is a gray area — current guidance is that operators adopt the strictest jurisdiction their node's region subjects them to. A node cannot shed exposure by flipping regions after the fact: slash eligibility is evaluated against the region in force when the bytes were served. + +## Sub-day removal orders + +Some statutory regimes bind the operator that receives a removal order to a sub-day deadline — the EU Terrorist Content Online Regulation's one-hour clock is the tightest. Such an order is discharged at the operator level, on the [local denylist](#local-denylist): it takes effect on the next reload, needs no governance round-trip, and binds exactly what the order binds — the recipient's own serving. Network-wide removal on that clock goes through the emergency multisig. -A node serving a globally-blacklisted hash is always slashable. A node in `region: DE` serving a `DE`-regional entry is slashable; that same node serving the entry via a US client is a gray area — current guidance is that operators adopt the strictest jurisdiction their node's region subjects them to. +## Origin assignment + +The blacklist is the DAO's authority to _remove_ an origin. `OriginAssignment` is the symmetric authority to _grant_ one, and it separates two planes: + +- **Vetting** is governance's job, done once per publisher wallet under a swappable vetting policy. +- **Seating** is the publisher's job. A vetted publisher picks its own origin operators from the bonded set, per namespace, instantly and with no further governance action. Multiple operators may serve one namespace, so redundancy is a namespace-level property. + +Without positive authority, origin assignment would be entirely off-protocol: no Sybil resistance on origin claims, no enforced redundancy, and no way to pre-authorize rather than punish after the fact. Cache-only serving remains permissionless throughout — only the origin role is gated here. ## Origin blacklisting -Re-uploading blacklisted content under a fresh hash (trivial re-encoding) is defeated by **origin blacklisting** — the blacklist contract can blacklist an **operator address**. Re-uploading requires fresh stake under a new identity. This raises the cost of hash evasion from "re-encode" to "spin up a new operator, restake, re-register". +Re-uploading blacklisted content under a fresh hash (trivial re-encoding) is defeated by **origin blacklisting** — the blacklist contract can blacklist an **operator address**. A blacklisted operator is ejected from the registry, drops out of every namespace's authorized origin set, has its remaining bond enter forced unbonding, and cannot re-register under the same address while the entry stands. It cannot self-reinstate by re-bonding: lifting the ban is a governance action. + +Ejection is not slashing — the bond is returned after unbonding absent a separate offense. Serving a blacklisted hash is the slashable act; repeatedly sourcing blacklisted content is what gets an operator ejected. Together they raise the cost of hash evasion from "re-encode" to "spin up a new operator, restake, re-register". ## Local denylist -Each node maintains a **local denylist** for direct legal notices (e.g., a DMCA takedown served to one operator). Local denylists don't propagate and don't trigger slashes elsewhere — they're a per-operator compliance tool. +Each node maintains a **local denylist** for direct legal notices (e.g., a DMCA takedown served to one operator). Local denylists don't propagate and don't trigger slashes elsewhere — they're a per-operator compliance tool, and the fastest removal path the protocol offers. ## Slash schedule From d28c3057b5330c51238bb79fa8a93548d7089d01 Mon Sep 17 00:00:00 2001 From: yigitdot Date: Thu, 3 Sep 2026 05:05:36 +0300 Subject: [PATCH 2/6] docs: clarify the mb unit, corruption path, and challenge bond Co-Authored-By: Claude Opus 5 --- docs/protocol/payments.mdx | 4 +++- docs/protocol/slashing.mdx | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/protocol/payments.mdx b/docs/protocol/payments.mdx index b6e6837..f72cc0a 100644 --- a/docs/protocol/payments.mdx +++ b/docs/protocol/payments.mdx @@ -59,7 +59,9 @@ claimed = amount + chain_index × chunk_price A preimage is self-proving — nobody derives a deeper one without the seed — so accepting a tick costs one hash, with no signature, no round trip and nothing to persist before the next chunk ships. Signatures are therefore O(1) per transfer rather than one per interval. -The payment quantum is `chunk_bytes` = **1 MiB**, a protocol constant. Nothing negotiates it and no message carries it: the payer and node must price a released preimage identically, and a fixed constant removes the disagreement rather than negotiating it away. Because a chunk is exactly one MB of price, `chunk_price` is the node's advertised `rate_per_mb` — and the node rejects any voucher whose signed `chunk_price` does not equal its own quote. +The payment quantum is `chunk_bytes` = **1 MiB (1,048,576 bytes)**, a protocol constant. Nothing negotiates it and no message carries it: the payer and node must price a released preimage identically, and a fixed constant removes the disagreement rather than negotiating it away. + +The protocol's **MB is that same 1,048,576 bytes** — the price unit and the payment quantum are one number, so a chunk costs exactly the node's advertised `rate_per_mb` with no rounding at any rate. `chunk_price` is therefore just that quoted rate, signed into the voucher, and the node rejects any voucher whose signed `chunk_price` does not equal its own quote. ## Credit window diff --git a/docs/protocol/slashing.mdx b/docs/protocol/slashing.mdx index 17187f4..772cf51 100644 --- a/docs/protocol/slashing.mdx +++ b/docs/protocol/slashing.mdx @@ -14,14 +14,14 @@ Every probe and stream response carries a mandatory signature over its security- ## What is not slashable -- **Corrupted delivery.** Absorbed at the wire: the client verifies each chunk group as it arrives and signs no voucher for a corrupt window, so the node ships garbage unpaid. There is no on-chain path. +- **Corrupted delivery.** Absorbed at the wire: the payer verifies each chunk group as it arrives and pays only behind verification — it withholds the preimage that would meter a corrupt chunk, and signs no voucher covering it. The node ships garbage unpaid, and its local reputation drops. There is no on-chain path. - **Refusing to serve.** A node may decline any request — for price, capacity, or policy reasons. The client re-routes to another provider and pays only a latency cost. ## Commit–reveal challenges Submitting evidence is a two-phase flow: a `commitChallenge` registers an opaque commitment that moves no funds, and the reveal resolves synchronously once the commitment matures. -The phases exist because the reveal must disclose the full evidence — the signed responses themselves — in its calldata. Without a prior commitment, a mempool watcher could copy a pending honest challenge, resubmit it under its own address, and capture the challenger reward. The bond round-trips either way, so only the reward is at risk; but that reward is the entire incentive for off-path witnesses, so leaving it extractable would hollow out enforcement. +The phases exist because the reveal must disclose the full evidence — the signed responses themselves — in its calldata. Without a prior commitment, a mempool watcher could copy a pending honest challenge, resubmit it under its own address, and capture the challenger reward. The bond is never what's at stake here — it is returned on a successful challenge and never transferred at all on a failed one — so the reward is the whole exposure, and it is also the entire incentive for off-path witnesses. Leaving it extractable would hollow out enforcement. ## Evidence staleness From 50780b8789a1762a02e3f4c373205a8b8473d37b Mon Sep 17 00:00:00 2001 From: yigitdot Date: Thu, 3 Sep 2026 12:19:05 +0300 Subject: [PATCH 3/6] docs: correct claims flagged by review against the spec Co-Authored-By: Claude Opus 5 --- docs/overview/architecture.mdx | 5 ++-- docs/overview/design-decisions.mdx | 4 +-- docs/overview/glossary.mdx | 6 +++-- docs/overview/how-it-works.mdx | 6 ++--- docs/overview/participants.mdx | 18 +++++++------- docs/protocol/content-addressing.mdx | 14 +++++------ docs/protocol/governance.mdx | 37 ++++++++++++++++------------ docs/protocol/network.mdx | 4 +-- docs/protocol/payments.mdx | 14 +++++------ docs/protocol/privacy.mdx | 26 ++++++++++--------- docs/protocol/reputation.mdx | 4 ++- docs/protocol/slashing.mdx | 8 +++++- docs/protocol/takedown.mdx | 4 +-- 13 files changed, 83 insertions(+), 67 deletions(-) diff --git a/docs/overview/architecture.mdx b/docs/overview/architecture.mdx index 5e669d9..355d515 100644 --- a/docs/overview/architecture.mdx +++ b/docs/overview/architecture.mdx @@ -48,9 +48,9 @@ See [Participants](/overview/participants) for a deeper breakdown per role. 1. **Client bootstrap** — client generates a keypair, queries the on-chain registry for the active node set, and caches it. 2. **Discovery** — a DHT lookup locates holders of the blob hash, and a parallel probe confirms which of them will actually serve the wanted range, at what rate and latency. -3. **Selection** — returned candidates are scored by price, latency, and reputation; lowest cost wins. +3. **Selection** — returned candidates are scored by price, latency, and reputation into one selection score; the lowest score wins, so a cheap or nearby node can outrank a better-reputed one. 4. **Payment** — the client pays from a USDC pool it opened once, off the fetch path, and reuses for every node ([payments](/protocol/payments)). -5. **Streaming + vouchers** — node streams to the client; client signs a cumulative voucher and releases one hash-chain preimage per megabyte as bytes flow. +5. **Streaming + vouchers** — node streams to the client; client signs a cumulative voucher and, on an optional hash chain, releases one preimage per 1 MiB chunk as bytes flow. 6. **Cache-miss fan-out** — if the node doesn't have the blob, it pulls from other holders (paid). Every byte delivered in the network is paid. 7. **Redemption** — the node redeems its own vouchers on-chain whenever it likes; the owner closes the pool to reclaim the unspent remainder. @@ -71,3 +71,4 @@ See [Participants](/overview/participants) for a deeper breakdown per role. - Search, discovery, or recommendation - Mobile or web clients - Erasure coding — full replication only +- Multi-chain settlement — a single L2 diff --git a/docs/overview/design-decisions.mdx b/docs/overview/design-decisions.mdx index 0b2576e..3d9ffd5 100644 --- a/docs/overview/design-decisions.mdx +++ b/docs/overview/design-decisions.mdx @@ -1,9 +1,9 @@ --- title: Design decisions -description: The accepted architecture decision records that define deCDN — topology, content addressing, payments, reputation, governance, slashing, tokenomics. +description: The architecture decision records that define deCDN — topology, content addressing, payments, reputation, governance, slashing, tokenomics. --- -The protocol is shaped by a set of accepted ADRs. The table below names each one. Full text is not yet public; numbering gaps reflect superseded or withdrawn proposals. +The protocol is shaped by a set of ADRs, which together are its canonical specification. The table below names each one; individual records range from draft to accepted. Full text is not yet public, and numbering gaps reflect superseded or withdrawn proposals. | ADR | Decision | | --- | ------------------------------ | diff --git a/docs/overview/glossary.mdx b/docs/overview/glossary.mdx index afe412e..a806a21 100644 --- a/docs/overview/glossary.mdx +++ b/docs/overview/glossary.mdx @@ -16,10 +16,11 @@ description: Definitions for the core deCDN terms — bao verified streaming, bl | **DHT** | Kademlia-subset content discovery. Nodes self-publish records, with coverage, when caching a blob. | | **Full holder** | A node caching a complete blob, as opposed to a partial holder caching only some blocks. Both are eligible sources for a multi-source fetch. | | **Hash sequence** | An ordered collection of blob hashes (a directory or manifest equivalent). | -| **Multi-source fetch** | Fetching one large blob from several holders in parallel, each serving disjoint verifiable ranges, up to a configurable maximum (`max_sources`) and at most one node per operator. Each source is an ordinary paid stream — no new wire protocol. | +| **Lane** | The payment relationship between one signer and one node: an independent running total with its own watermark, hash chain, and credit window. A pool backs many lanes at once. | +| **Multi-source fetch** | Fetching one large blob from several holders in parallel, each serving disjoint segments, up to a configurable maximum (`max_sources`) and at most one node per operator. Each source is an ordinary paid stream — no new wire protocol. | | **Namespace** | A publisher-owned identifier for a content set, and the unit of origin addressing. A request pairs it with the blob hash; the default namespace names no publisher and no authorized origins. | | **Node** | A bonded QUIC endpoint that caches and delivers blobs. Some are origin-backed; others are pure caches. | -| **Origin-backed node** | A node configured with an S3-compatible store, NFS mount, or local disk — the canonical source for specific blobs. Never experiences a true cache miss. | +| **Origin-backed node** | A node configured with an S3-compatible store, NFS mount, or local disk — the canonical source for specific blobs. Never experiences a true cache miss for blobs in that store; for anything else it is a cache node. | | **Origin backend** | The opaque storage behind an origin-backed node (S3, R2, B2, MinIO, NFS, local disk). Never exposed to the network. | | **Outboard** | A blob's BLAKE3 hash tree, stored separately from its content. An origin-cold node must import or build the outboard before it can serve verified ranges. | | **Payment chunk** | The payment quantum: 1 MiB, a protocol constant. One released preimage pays for exactly one chunk. Distinct from the 16 KiB bao chunk group, which is the unit of verification. | @@ -29,6 +30,7 @@ description: Definitions for the core deCDN terms — bao verified streaming, bl | **Publisher** | An address that owns at least one namespace. Once governance has vetted the publisher wallet, it seats and unseats the origin operators for its own namespaces directly. | | **Segment** | A contiguous, verifiably-aligned byte range assigned to a single source during a multi-source fetch. Independently verifiable, so it can be split or reassigned without re-reading from the start. | | **Selection score** | A combined ranking over advertised price, observed latency, and reputation. Lower is better. | +| **Slashing window** | The 30 seconds for which a node's last probe-quoted rate is binding. A stream opened inside it must be served at or below that quote; serving higher is slashable. | | **TOKEN** | The deCDN bonding/governance token: locked as a node's capacity bond and burned by the buyback sink. Not used for payments; governance weight comes from delivered bytes, not TOKEN balance. | | **USDC** | The payment currency for all delivery. Fixed at deployment; pools deposit, voucher, and settle in USDC. | | **Voucher** | A signed off-chain payment message naming its payee node and carrying a cumulative amount owed. The highest amount settles; a re-submitted voucher pays zero. | diff --git a/docs/overview/how-it-works.mdx b/docs/overview/how-it-works.mdx index e07384f..b87286f 100644 --- a/docs/overview/how-it-works.mdx +++ b/docs/overview/how-it-works.mdx @@ -13,7 +13,7 @@ sequenceDiagram participant P as PaymentPool (on-chain) C->>N: Probe(hash) - N-->>C: Probe response (coverage, rate, signed) + N-->>C: Probe response (rate signed; coverage unsigned) C->>N: Stream request(hash, namespace) N-->>C: Bytes (hash-verified on receipt) C-->>N: Voucher (signed, cumulative) @@ -55,7 +55,7 @@ For a large blob, the client doesn't pull from a single node. A client-side sche Assignment follows **coverage**: discovery and probes carry a bitmap of which 64 MiB blocks each holder will serve, so each block goes to a holder that already has it. A block no admitted holder covers is a genuine gap, warmed once from origin — and that warmer's new coverage becomes discoverable supply for the next fetch. -Assignment is dynamic. When a source finishes, it steals the largest remaining range from a peer, splitting it on a verifiable boundary; every range is verified as it lands, and a stalled or failed source's _unfinished remainder_ is reassigned to another source without restarting the download. There is deliberately no hedging — racing a range against idle sources would mean paying for the copies that lose, so the tail is bounded by deadline-based reassignment instead. Aggregating parallel streams lets a blob be delivered at multi-gigabit speeds once enough holders cover it — no single origin's uplink caps the transfer. +Assignment is dynamic. When a source finishes its **segment**, it steals the largest remaining one from a peer, splitting it on a verifiable boundary; every segment is verified as it lands, and a stalled or failed source's _unfinished remainder_ is reassigned without restarting the download. There is deliberately no hedging — racing a segment against idle sources would mean paying for the copies that lose, so the tail is bounded by deadline-based reassignment instead. Aggregate throughput therefore scales with the number of admitted sources rather than any one holder's uplink. There is no new wire surface — each source is just an ordinary paid stream, so pools, vouchers, and slashing work exactly as in the single-source paths above. Every source draws on the client's one pool, on its own independent lane. @@ -63,7 +63,7 @@ There is no new wire surface — each source is just an ordinary paid stream, so 1. **Open** — the payer escrows one USDC deposit in the `PaymentPool` contract. Once, off the fetch path, reused for every node. 2. **Delegate** — the owner signs capped, expiring capabilities for whichever keys may spend from the pool. One capability is valid at every node. -3. **Meter** — a signed cumulative voucher anchors the amount owed, and a released hash-chain preimage advances it one megabyte at a time with no further signature. +3. **Meter** — a signed cumulative voucher anchors the amount owed, and an optional hash chain advances it by one 1 MiB **payment chunk** per released preimage, with no further signature. A voucher opening no chain is sealed at the amount it signs. 4. **Redeem** — the node settles its own vouchers on-chain whenever it likes; the owner closes the pool to reclaim the remainder, after a grace window that lets nodes redeem first. ## What keeps the network honest diff --git a/docs/overview/participants.mdx b/docs/overview/participants.mdx index 28d30a9..5ead605 100644 --- a/docs/overview/participants.mdx +++ b/docs/overview/participants.mdx @@ -22,22 +22,22 @@ Two deployment shapes: | **Pure cache** | No origin backend. Pulls missed blobs from other nodes. | You want to earn on popular content; you don't control any origin. | | **Origin-backed** | Configured with an S3-compatible backend, NFS mount, or local disk. The hash→object-key mapping is a local content catalog. | You are the content provider, or you are running a "last-mile" seed node for a provider. | -"Origin-backed" is a deployment choice, not a protocol distinction — the network cannot tell them apart at runtime. +"Origin-backed" is a deployment choice, not a protocol distinction — the network cannot tell them apart at runtime. It is also per-blob: for anything outside its own backend, an origin-backed node is a cache node and pays upstream like any other. ## Clients A **client** is a lightweight QUIC endpoint that consumes content and pays for it. -- **Identity.** Ephemeral network keypair — no stake, no on-chain registration. +- **Identity.** A network keypair generated at first startup and persistent across sessions, plus a per-connection binding to an Ethereum address that the node discards on disconnect — no stake, no on-chain registration. - **Discovery.** Reads the on-chain registry for its peer set and queries the DHT for holders. Publishes nothing and holds no network role. - **Payment.** Opens one on-chain USDC pool, reuses it for every node it buys from, and signs cumulative off-chain vouchers as bytes flow ([payments](/protocol/payments)). - **Trust model.** Verifies hashes on all bytes received. Trusts no node for content, and none for availability either — a node may decline to serve, and the client simply re-routes without paying for the attempt. -Clients can run a multi-source parallel download for large files, pulling disjoint ranges from several holders at once, and resume crashed transfers from the last verified byte. +Clients can run a multi-source parallel download for large files, pulling disjoint segments from several holders at once, and resume crashed transfers from the last verified chunk-group boundary. ## External components -These are **not CDN protocol participants**. They ride the same QUIC transport but do not register, probe, or bond. +These are **not CDN protocol participants**. A node reaches its backend over that backend's own interface — HTTP, the S3 API, a filesystem — not over a CDN protocol. A backend never registers, probes, or bonds. ### Origin backend @@ -45,8 +45,8 @@ An S3 bucket, R2 bucket, MinIO instance, NFS mount, or local disk. Holds the can ## Who bonds, who pays -| Participant | Bonds? | Pays? | Earns? | -| ------------------ | -------- | ------------------------------ | ------------------------- | -| Pure cache node | ✅ TOKEN | ✅ Pays upstream on cache miss | ✅ Per-MB voucher revenue | -| Origin-backed node | ✅ TOKEN | ❌ | ✅ Per-MB voucher revenue | -| Client | ❌ | ✅ Per-MB voucher | ❌ | +| Participant | Bonds? | Pays? | Earns? | +| ------------------ | -------- | -------------------------------- | ------------------------- | +| Pure cache node | ✅ TOKEN | ✅ Pays upstream on cache miss | ✅ Per-MB voucher revenue | +| Origin-backed node | ✅ TOKEN | ✅ For blobs outside its backend | ✅ Per-MB voucher revenue | +| Client | ❌ | ✅ Per-MB voucher | ❌ | diff --git a/docs/protocol/content-addressing.mdx b/docs/protocol/content-addressing.mdx index 31b5e92..6e2783a 100644 --- a/docs/protocol/content-addressing.mdx +++ b/docs/protocol/content-addressing.mdx @@ -18,7 +18,7 @@ Delivery uses **bao** — an interleaved verified-stream encoding of the BLAKE3 Because every chunk group carries its own proof, a resumed or ranged fetch verifies **independently** — the decoder aligns the requested offset down to its 16 KiB chunk-group boundary, so there's no need to re-read from the start. Payment meters all transmitted bytes, including the proof bytes: the verification overhead is roughly **0.4%** of blob size, not free metadata. -An origin-cold node must import or build the blob's **outboard** (its BLAKE3 tree) before it can serve verified ranges. Node-to-node cache-miss pulls stay bao-encoded and pipelined end to end. +Where an origin publishes the blob's **outboard** (its BLAKE3 tree) as a sidecar, an origin-cold node streams the origin's bytes straight through to the paying client while teeing them into its own store, verifying each group against the root as it goes. Only when the origin publishes no outboard must the node import the blob and build one first, at a one-time import latency. Node-to-node cache-miss pulls stay bao-encoded and pipelined end to end. ## Publishers and namespaces @@ -43,9 +43,9 @@ This also protects origin operators from direct egress cost attacks: an adversar ## What clients verify vs. trust -| Property | Verified | Trusted | -| ----------------- | ---------------- | ------------------------------------------------------------------- | -| Byte correctness | BLAKE3 hash tree | — | -| Blob availability | — | Node may decline to serve; the client re-routes and re-pays nothing | -| Rate honesty | — | Quoted rate is binding within the slashing window (slashable) | -| Origin durability | — | Origin operator's S3/NFS configuration (off-protocol) | +| Property | Verified | Trusted | +| ----------------- | ---------------- | ------------------------------------------------------------------------------------------------------- | +| Byte correctness | BLAKE3 hash tree | — | +| Blob availability | — | Node may decline to serve; the client re-routes and re-pays nothing | +| Rate honesty | — | Quoted rate is binding within the [slashing window](/protocol/slashing#the-slashing-window) (slashable) | +| Origin durability | — | Origin operator's S3/NFS configuration (off-protocol) | diff --git a/docs/protocol/governance.mdx b/docs/protocol/governance.mdx index ee9cf37..6c5b7cc 100644 --- a/docs/protocol/governance.mdx +++ b/docs/protocol/governance.mdx @@ -5,14 +5,15 @@ description: Voting weight is bytes actually delivered, not TOKEN held — a 4% ## Decision -OpenZeppelin Governor whose voting weight is an operator's **delivered bytes over a trailing window** — not TOKEN balance — with a **4% quorum** and a **2-day timelock**. All governable parameters have hardcoded safety bounds that governance cannot override. A **3-of-5 emergency multisig** can pause contracts, add emergency blacklist entries, decide slash appeals, and suspend a compromised regional governance body. Its sunset is **split by capability**: the protocol-wide `pause()` brake has a **12-month sunset** enforced via an immutable constructor deadline — governance cannot extend it; prolonging `pause()` past the deadline requires deploying a new contract version and migrating through the standard upgrade path. The narrow unlawful-content-removal power — emergency blacklisting — is **permanent**, discharging an ongoing legal-compliance duty the ~11-day governance takedown path cannot meet. +OpenZeppelin Governor whose voting weight is an operator's **delivered bytes over a trailing window** — not TOKEN balance — with a **4% quorum** and a **2-day timelock**. All governable parameters have hardcoded safety bounds that governance cannot override. A **3-of-5 emergency multisig** can pause contracts, add emergency blacklist entries, decide slash appeals, and suspend a compromised regional governance body. Its sunset is **split by capability**: the protocol-wide `pause()` brake has a **12-month sunset** enforced via an immutable constructor deadline — governance cannot extend it; prolonging `pause()` past the deadline requires deploying a new contract version and migrating through the standard upgrade path. The narrow unlawful-content-removal power — emergency blacklisting — is **permanent**, discharging an ongoing legal-compliance duty the ~10-day governance cycle cannot meet. ## Voting weight -Voting weight is each operator's **served bytes over a trailing window** (default ~1 quarter, governable), scaled by a tenure ramp and capped per operator (default 5% of the window's bytes). +Voting weight is each operator's **served bytes over a trailing window** (default ~1 quarter, governable), scaled by a tenure ramp and capped per operator (default 5% of the window's bytes). Bytes are counted a week at a time, and each week's count is additionally capped at what the capacity the operator declared that week could physically have delivered. - **Passive TOKEN holders have no vote.** Governance power tracks real delivery, not holdings — you must operate a node and serve bytes to vote. - **Fresh operators ramp in.** A new operator that serves heavily on day one still votes at a fraction of its byte share until the tenure ramp completes over several months. +- **Declared capacity caps weight; it never grants it.** Bytes above what an operator's declared tier could deliver in a week earn no weight, and an operator that declared zero capacity contributes zero for that week however many bytes it served. This is what stops a lightly-bonded operator inflating its weight by serving traffic to accounts it controls. - **Slashing zeroes out the vote.** A slashed operator's weight drops to zero for the rest of the window, then recovers as the window slides past the slash. Bytes are read from the on-chain `FeeRouter` settlement counter, so weight is auditable and cannot be claimed without paid delivery. Voting power is delegable by signed message; the bond itself is not. @@ -27,20 +28,20 @@ The phase ends when the multisig judges the operator set broad and diverse enoug Even governance cannot set parameters outside these bounds: -| Parameter | Default | Bounds | -| ----------------------------------------- | --------- | ------------- | -| Operator revenue share (`FeeRouter` base) | 60% | 40% – 90% | -| Burn share (`FeeRouter`) | 30% | 5% – 50% | -| Treasury share (`FeeRouter`) | 10% | 0% – 30% | -| Redemption grace window | 48 h | 48 h – 72 h | -| Per-operator voting cap | 5% | 1% – 25% | -| Trailing vote window | 13 epochs | 4 – 26 epochs | -| Capacity-curve exponent (α) | 1.2 | 1.0 – 1.8 | -| Unbonding window | 14 days | 7 – 60 days | +| Parameter | Default | Bounds | +| ----------------------------------------- | -------- | ------------ | +| Operator revenue share (`FeeRouter` base) | 60% | 40% – 90% | +| Burn share (`FeeRouter`) | 30% | 5% – 50% | +| Treasury share (`FeeRouter`) | 10% | 0% – 30% | +| Redemption grace window | 48 h | 48 h – 72 h | +| Per-operator voting cap | 5% | 1% – 25% | +| Trailing vote window | 13 weeks | 4 – 26 weeks | +| Capacity-curve exponent (α) | 1.2 | 1.0 – 1.8 | +| Unbonding window | 14 days | 7 – 60 days | The three `FeeRouter` shares must sum to exactly 100% on every update. The burn share is the one that also accepts **zero** — the dormant launch configuration, with the split at 90/0/10 until governance activates the buyback sink toward the 60/30/10 steady state. It is therefore either off or at 5% or more; governance cannot leave it nominally on at a negligible share. -Bounds are enforced in `require()` checks in the setter functions. A malicious governance proposal that attempts to push the operator revenue share below its floor simply reverts. (Slashing tiers — 5% / 15% / 50% by lifetime offense count — are fixed constants, not governable parameters; see [slashing](/protocol/slashing).) +Bounds are enforced in `require()` checks in the setter functions. A malicious governance proposal that attempts to push the operator revenue share below its floor simply reverts. (The 5% / 15% / 50% slash tiers are themselves governable, within a hardcoded 5%–50% per-offense bound. What governance cannot touch is the 1× / 2× / 4× escalation applied to the _reset period_ on lifetime offense count — that is hardcoded so the anti-gaming curve cannot be flattened. See [slashing](/protocol/slashing).) ## Emergency multisig @@ -49,7 +50,7 @@ Bounds are enforced in `require()` checks in the setter functions. A malicious g - **Pause** any contract (`Pausable`) - **Add emergency blacklist entries** (with 14-day auto-expiry or 90-day for CSAM/terrorist categories — see [takedown](/protocol/takedown)) - **Decide slash appeals** — fast-track or reject an open appeal; the escrow movement itself is performed deterministically by the bond contract -- **Suspend a regional governance body** — subject to governance ratifying or reversing within 14 days +- **Suspend a regional governance body** — governance must ratify or reverse within 14 days, and silence _lapses_ the suspension rather than sustaining it Can **not**: @@ -57,7 +58,11 @@ Can **not**: - Change parameters - Grant roles -The sunset is **split by capability**. The protocol-wide `pause()` brake carries a **12-month sunset** enforced in the multisig's constructor as an immutable deadline — after it, `pause()` reverts on-chain. Emergency **blacklisting does not sunset**: `emergencyAdd()`, `emergencyAddOrigin()`, and `suspendRegionalBody()` are permanent, because unlawful-content removal (CSAM / court-ordered / sanctions) is a time-critical legal duty the ~11-day governance takedown path cannot meet, and touches no economic, treasury, or governance lever. Governance **cannot** modify the `pause()` deadline; retaining `pause()` past the sunset requires deploying a new contract version with a new deadline and migrating to it via the standard upgrade path (timelock + governance vote). This guarantees the `pause()` sunset cannot be silently extended. +The sunset is **split by capability**, and the split is structural rather than a shared clock. The 12-month deadline attaches to **`pause()` alone**: every pausable contract fixes it as an immutable at its own construction, after which `pause()` reverts for every caller — governance included. The pause is the protocol-wide brake and the most centralization-sensitive power the multisig holds, so it is the one that expires. + +The other three capabilities carry **no deadline**. Emergency blacklisting (`emergencyAdd`, `emergencyAddOrigin`) and the regional-body suspension backing it live on the non-pausable blacklist contract, discharging a permanent, time-critical legal duty — CSAM, court-ordered and sanctions removal — that the ~10-day governance cycle cannot meet, and touching no economic, treasury or governance lever. Deciding slash appeals likewise never expires; it moves only already-escrowed funds along deterministic grant/uphold paths. + +Governance **cannot** modify the `pause()` deadline; retaining `pause()` past the sunset requires deploying a new contract version with a new deadline and migrating to it via the standard upgrade path (timelock + governance vote). This guarantees the sunset cannot be silently extended. ## Proposal flow @@ -72,7 +77,7 @@ The combined ~10-day minimum delay (voting delay + 7-day period + 48-hour timelo ## Why 4% quorum -Low enough that realistic participation can reach quorum; high enough that no single operator can unilaterally pass a proposal — and the per-operator weight cap bounds concentration directly. 4% maps to established DAO practice (Compound, Uniswap) and works in conjunction with the hardcoded safety bounds — the bounds mean a bad quorum can still only move parameters within the safe range. +Low enough that realistic participation can reach it — voting weight tracks active service delivery rather than circulating supply, so a bar calibrated against holdings would quickly exceed engaged voting power. Quorum is not the concentration defense and is not sized as one: the per-operator cap is what bounds any single operator. Note that at their defaults the cap (5%) sits above the quorum bar (4%), so quorum alone does not stop one operator reaching it. The hardcoded safety bounds do the rest — even a captured vote can only move parameters within the safe range. ## Regional governance bodies diff --git a/docs/protocol/network.mdx b/docs/protocol/network.mdx index e9e1a3e..7cbec08 100644 --- a/docs/protocol/network.mdx +++ b/docs/protocol/network.mdx @@ -17,7 +17,7 @@ The same active set seeds the DHT routing table, so a freshly started node parti A cache miss issues a DHT lookup for the hash, which returns candidate holders along with a coarse **coverage bitmap** per holder — so a requester can prune candidates that do not hold the range it wants. Candidates are then probed in parallel to confirm live availability, fresh coverage, latency and price. Collection stops as soon as enough blob-holding candidates answer to fill the failover budget, and otherwise at a 500 ms ceiling that accommodates inter-continental round trips. -When a lookup returns no providers, the on-chain origin directory is the deterministic fallback. If that returns nothing either, the blob is not available in the network. +When a lookup returns no providers for a request that names a namespace, the namespace's on-chain origin set is the deterministic fallback. A request naming no namespace has no directory to fall back to — it has no authorized origins — so a miss there simply fails. Either way, if nothing answers, the blob is not available in the network. ## Selection @@ -35,4 +35,4 @@ Regions inform selection rather than partition the mesh: a client in Frankfurt p ## NAT traversal -The transport handles hole-punching automatically; nodes update their registered addresses when their public-facing address changes. When direct P2P fails, traffic routes through a stateless, content-blind QUIC relay. Relays are protocol infrastructure (treasury-funded), not an incentivized network role — paying relays per byte would create a perverse incentive to prevent direct connections. +The transport handles hole-punching automatically; nodes update their registered addresses when their public-facing address changes. When direct P2P fails, traffic routes through a stateless, content-blind QUIC relay. Relays are not protocol participants — they cannot inspect, cache, or modify content — and they are not an incentivized network role: paying relays per byte would create a perverse incentive to prevent direct connections. Deployments self-host them as operational infrastructure, funded from the protocol treasury or from node staking fees. diff --git a/docs/protocol/payments.mdx b/docs/protocol/payments.mdx index f72cc0a..9773b0a 100644 --- a/docs/protocol/payments.mdx +++ b/docs/protocol/payments.mdx @@ -5,7 +5,7 @@ description: Clients pay nodes per MB with off-chain vouchers backed by one shar ## Decision -Clients pay nodes **per MB** with off-chain **vouchers backed by a shared on-chain pool**. All pools are denominated in **USDC**, fixed at contract deployment. On a cache miss, nodes pay peers per MB for content pulls, then amortize that cost across many client deliveries. Origin-backed nodes set the effective price ceiling (their backend egress costs). Rates are fully market-driven within governance-set bounds. +Clients pay nodes **per MB** with off-chain **vouchers backed by a shared on-chain pool**. All pools are denominated in **USDC**, fixed at contract deployment. On a cache miss, nodes pay peers per MB for content pulls, then amortize that cost across many client deliveries. Origin-backed nodes set the effective price ceiling (their backend egress costs). Rates are otherwise fully market-driven, above a governance-set floor. One funded pool backs payments from **many capped signers** to **many nodes**. There is no channel per node and no channel per client: the owner opens a pool once, reuses it, and closes it to reclaim the unspent remainder. @@ -49,7 +49,7 @@ Off-chain signed messages over `{pool_id, signer, provider, amount, bytes_delive ## Hash-chain metering -Between signatures, delivery is metered by a **hash chain** rather than a new signature per interval. The payer commits `chain_root = keccak^255(seed)` in the voucher, and after verifying chunk `k` releases `keccak^(255−k)(seed)`. The node hashes that preimage forward until it reaches a value it already trusts. +Between signatures, delivery is metered by a **hash chain** rather than a new signature per interval. The payer commits the chain's root in the voucher and, after verifying each delivered chunk, releases the next **preimage** down the chain. The node hashes that preimage forward until it reaches a value it already trusts. Redemption resolves both objects with one formula: @@ -59,18 +59,16 @@ claimed = amount + chain_index × chunk_price A preimage is self-proving — nobody derives a deeper one without the seed — so accepting a tick costs one hash, with no signature, no round trip and nothing to persist before the next chunk ships. Signatures are therefore O(1) per transfer rather than one per interval. -The payment quantum is `chunk_bytes` = **1 MiB (1,048,576 bytes)**, a protocol constant. Nothing negotiates it and no message carries it: the payer and node must price a released preimage identically, and a fixed constant removes the disagreement rather than negotiating it away. - -The protocol's **MB is that same 1,048,576 bytes** — the price unit and the payment quantum are one number, so a chunk costs exactly the node's advertised `rate_per_mb` with no rounding at any rate. `chunk_price` is therefore just that quoted rate, signed into the voucher, and the node rejects any voucher whose signed `chunk_price` does not equal its own quote. +The payment quantum is the **payment chunk**: `chunk_bytes` = **1 MiB (1,048,576 bytes)**, a protocol constant that nothing negotiates and no message carries. A released preimage has to be worth the same to payer and node, and a fixed constant removes that disagreement instead of negotiating it away. The protocol's **MB is that same 1,048,576 bytes**, so a chunk costs exactly the node's advertised `rate_per_mb` with no rounding at any rate — `chunk_price` is just that quoted rate, signed into the voucher, and a node rejects any metering voucher whose signed `chunk_price` does not match its own quote. A voucher that seals a lane meters nothing and carries `chunk_price = 0`, so the check does not apply to it. ## Credit window -A node's exposure is bounded by a per-lane **credit window** — the unpaid balance it will carry before it pauses — not by the meter's resolution. The window starts at a floor and ramps as the lane pays, so a new payer proves itself on small credit before a node extends more. +Payments from one signer to one node out of one pool form a **lane**: an independent running total, with its own watermark and its own chain. A node's exposure on a lane is bounded by a **credit window** — the unpaid balance it will carry before it pauses — not by the meter's resolution. The window starts at a floor and ramps as the lane pays, so a new payer proves itself on small credit before a node extends more. ## Rate discovery -Each node advertises its own per-MB rate, quoted in every signed probe response. Governance sets rate bounds (in USDC); a node advertising outside the bounds is ignored by selection. A quoted rate is binding for any stream opened inside the slashing window ([slashing](/protocol/slashing)). +Each node advertises its own per-MB rate, quoted in every signed probe response. Governance sets a **floor**, not a ceiling: a node whose configured rate falls below the current floor raises its quote to the floor and logs a warning rather than refusing to serve, and redemption clamps the byte credit on any voucher priced under it. No governance ceiling caps a node's rate from above — only the wire format's own upper bound — because the buyer sees the signed rate before it pays anything, which is what makes a ceiling unnecessary. A requester rejecting an expensive quote is local policy rather than a protocol rule. A quoted rate is binding for any stream opened inside the [slashing window](/protocol/slashing#the-slashing-window). ## Minimum deposit -`openPool` enforces a governance-set `minDeposit` on the credited deposit. It is a Sybil knob: the per-pool solvency floor bounds loss within one pool, and an attacker escapes it by fanning out across many pools, so the floor taxes the capital locked in simultaneous pools. Deposits stay fully refundable, and the parameter **ships dormant at zero** so small honest pools remain available; governance arms it if Sybil pressure warrants. +`openPool` enforces a governance-set `minDeposit` on the credited deposit. It is a Sybil knob: the per-pool solvency floor bounds loss within one pool, and an attacker escapes it by fanning out across many pools, so the floor taxes the capital locked in simultaneous pools. Deposits stay fully refundable, and the parameter is bounded at **0 to $100** — there is no floor on the knob itself, and the ceiling keeps governance from pricing small honest buyers out of opening a pool. It **ships dormant at zero**; governance arms it if Sybil pressure warrants. diff --git a/docs/protocol/privacy.mdx b/docs/protocol/privacy.mdx index 61175e6..b82d218 100644 --- a/docs/protocol/privacy.mdx +++ b/docs/protocol/privacy.mdx @@ -9,28 +9,30 @@ Privacy is treated as an **accountability-first** design: probes are public, on- ## Adversary model (four tiers) -| Tier | Example adversary | What they can observe | -| --------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------- | -| **Passive observer** | On-path network middlebox | QUIC metadata (IPs, SNI unless ECH is deployed), timing, volume | -| **Active participant** | Malicious node or client | Probes, stream requests, payment pool opens and redemptions | -| **Infrastructure operator** | L2 RPC provider, relay host | On-chain events at read time; relay endpoints see encrypted QUIC flows | -| **Compromised endpoint** | Malicious origin or content-provider infrastructure | Plaintext bytes, client subscription identity | +Each tier subsumes the one above it. + +| Tier | Example adversary | What they can observe | +| --------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------- | +| **Passive observer** | ISP, blockchain analytics firm | QUIC connection metadata (IP pairs, timing, volume), the negotiated ALPN, and everything on-chain | +| **Active participant** | Competing CDN, curious node operator, researcher | Plus: probes it receives, stream requests, and the probe responses it collects | +| **Infrastructure operator** | RPC provider, relay host | Plus: which registry and blacklist queries a client makes, and IP pairs on relayed connections | +| **Compromised endpoint** | Device theft, malware, warrant | Plus: memory and disk on that one machine — including the network key, unencrypted today | ## Privacy surfaces The protocol inventories its distinct privacy surfaces with explicit dispositions (accept or mitigate). Highlights: - **On-chain settlement volume leakage.** Anyone can read pool deposits and per-node redemptions on-chain. Accepted: auditable settlement is what makes the payment layer trustless. -- **Signed protocol messages as non-repudiable content commitments.** A node that signs an availability response has provably committed to serving that hash. Accepted: the same signature is what enables on-chain accountability. -- **Probe traffic.** Who probes what is visible to the probed node. Accepted: selection depends on probing; fully private probing would require PIR at O(log N) blow-up. +- **Signed protocol messages as a content inventory.** A signed availability response is non-repudiable proof that the node held (or did not hold) a specific hash at a specific time, and accumulated signatures build a verifiable inventory. Accepted: the same signature is what enables on-chain accountability. +- **Probe traffic.** Who probes what is visible to every candidate probed, not just the one selected. Accepted: the delivering node must learn the hash regardless, so hiding the request from the others adds bandwidth without changing the underlying property. - **Registry enumeration.** Anyone can read the active set and each node's self-reported region and addresses. Accepted: an enumerable, slashable operator set is the point. ## Mitigations Both are pre-mainnet commitments: -1. **Client key encryption via platform keychain.** Clients store Ethereum keys in macOS Keychain, Windows Credential Manager, or Gnome Keyring — not plaintext files. -2. **Multi-source RPC.** Clients and nodes use multiple independent RPC providers to reduce single-vendor deanonymization surface. +1. **Client key encryption via platform keychain.** The client's network key sits unencrypted on disk today. Production moves it into the macOS Keychain, Windows Credential Manager, or the Linux Secret Service. (The Ethereum key is already held in an encrypted keystore.) +2. **Operational RPC guidance.** Document that clients and nodes should query through multiple independent RPC providers, or a self-hosted node, so no single vendor sees the whole query pattern. ## Assessed and not adopted @@ -40,5 +42,5 @@ Both are pre-mainnet commitments: ## Scope clarifications -- **Content plaintext** is a content-provider concern. If sensitivity requires it, the content provider encrypts before publishing. -- **Client IP addresses** are visible to peers. Tor-style relaying is out of scope — adding a layer of indirection conflicts with the pay-per-byte accountability model. +- **Content plaintext** is a content-provider concern, and application-level privacy is out of scope. If sensitivity requires it, the content provider encrypts before publishing. +- **Client IP addresses** are visible to peers, and to a relay host on any relayed connection. Network-level anonymity is not a protocol goal. diff --git a/docs/protocol/reputation.mdx b/docs/protocol/reputation.mdx index 718282f..6ddfdd8 100644 --- a/docs/protocol/reputation.mdx +++ b/docs/protocol/reputation.mdx @@ -28,9 +28,11 @@ interaction_score = 0.4 × speed + 0.4 × correctness + 0.2 × reachability Speed is normalized on a log curve against a node-local reference throughput (default 1 GiB/s) that deliberately **does not saturate** — a materially faster peer earns a materially better score instead of tying with every other fast peer. +A single interaction moves a peer's score by at most **±0.05**, so one bad exchange cannot destroy a good peer's standing. With α = 0.1 the EWMA itself keeps most deltas well under that; the clamp only binds when the gap between the peer's score and the interaction exceeds 0.5. + ## Why correctness is only one signal among several -Reputation measures service quality, not honesty. Corruption is absorbed at the wire: progressive BLAKE3 verification means a corrupt window yields no voucher, so the node ships garbage unpaid. The reputation hit compounds on top — a corruption event zeros the correctness component, drags the EWMA down, and degrades selection through a quadratic reputation penalty. Lost revenue plus lost traffic make sustained corruption irrational without an on-chain path. +Reputation measures service quality, not honesty. Corruption is absorbed at the wire: progressive BLAKE3 verification means a corrupt window yields no voucher, so the node ships garbage unpaid. The reputation hit compounds on top — a corruption event zeros the correctness component, moves the EWMA down by up to the per-interaction clamp, and degrades selection through a quadratic reputation penalty. Lost revenue plus lost traffic make sustained corruption irrational without an on-chain path. ## Decay toward neutral diff --git a/docs/protocol/slashing.mdx b/docs/protocol/slashing.mdx index 772cf51..47b8b11 100644 --- a/docs/protocol/slashing.mdx +++ b/docs/protocol/slashing.mdx @@ -7,7 +7,7 @@ description: Two slashable offenses — rate manipulation and blacklist violatio Two slashable offenses, each resting on cryptographically dispositive signed evidence: -1. **Rate manipulation** — charged a rate different from the rate quoted in a signed probe response within the slashing window. +1. **Rate manipulation** — charged a rate **higher** than the rate quoted in a signed probe response within the slashing window. (Charging less than quoted is not an offense.) 2. **Blacklist violation** — served a blacklisted hash after the compliance window. Every probe and stream response carries a mandatory signature over its security-relevant fields, produced with the node's Ethereum key, so any protocol message is portable evidence verifiable on-chain. A unified on-chain judge adjudicates both offenses synchronously and triggers the bond reduction immediately upon verification — there is no in-protocol counter-evidence window. The reduced bond is then held in escrow pending a post-slash appeal (below), not distributed on the spot. @@ -23,6 +23,12 @@ Submitting evidence is a two-phase flow: a `commitChallenge` registers an opaque The phases exist because the reveal must disclose the full evidence — the signed responses themselves — in its calldata. Without a prior commitment, a mempool watcher could copy a pending honest challenge, resubmit it under its own address, and capture the challenger reward. The bond is never what's at stake here — it is returned on a successful challenge and never transferred at all on a failed one — so the reward is the whole exposure, and it is also the entire incentive for off-path witnesses. Leaving it extractable would hollow out enforcement. +## The slashing window + +A node's last probe-quoted rate is binding for **30 seconds**. Any stream opened inside that window must be served at or below the quoted rate; a legitimate rate change waits the window out before serving at the new price. Two signed messages from the same node disagreeing about its own rate inside 30 seconds are non-repudiable, which is what makes the offense adjudicable without a counter-evidence step. + +This is distinct from **evidence staleness** below: the slashing window bounds when an offense can be _committed_, evidence age bounds how long it can be _submitted_. + ## Evidence staleness Evidence has a maximum age, set strictly below the unbonding period — so an accused cannot deregister and withdraw before being slashable. diff --git a/docs/protocol/takedown.mdx b/docs/protocol/takedown.mdx index 8cfe0db..e5c5000 100644 --- a/docs/protocol/takedown.mdx +++ b/docs/protocol/takedown.mdx @@ -41,9 +41,9 @@ The emergency path takes effect immediately in terms of "don't serve this", but ## Regional scoping -A **regional** entry applies only to nodes that advertise a matching region. The right to add regional entries is granted to regional governance bodies on a per-region basis; the emergency multisig can suspend a compromised body immediately, subject to governance ratifying or reversing the suspension within 14 days. Suspension bounds a body's future authority — entries it already issued stay live. +A **regional** entry applies only to nodes that advertise a matching region. The right to add regional entries is granted to regional governance bodies on a per-region basis; the emergency multisig can suspend a compromised body immediately. Governance must then ratify or reverse within 14 days, and **silence lapses the suspension** — the body resumes writing, exactly as an unratified emergency entry expires. Neither is a standing act of governance, and sustaining one on silence alone would let the multisig disable a jurisdiction permanently with no vote ever taken. Suspension also bounds only a body's _future_ authority: entries it already issued stay live. -A node serving a globally-blacklisted hash is always slashable. A node in `region: DE` serving a `DE`-regional entry is slashable; that same node serving the entry via a US client is a gray area — current guidance is that operators adopt the strictest jurisdiction their node's region subjects them to. A node cannot shed exposure by flipping regions after the fact: slash eligibility is evaluated against the region in force when the bytes were served. +Scope follows the **operator's** declared region — never the requester's. A node serving a globally-blacklisted hash is always slashable. A node declaring `DE` is slashable for a `DE`-regional entry; a node declaring `US` is not slashable for a hash blacklisted only by the EU body, regardless of where its clients are — the requester's location is not an input. A node cannot shed exposure by flipping regions after the fact either: slash eligibility is evaluated against the region in force when the bytes were served. ## Sub-day removal orders From be02454fc7b9d278a5c3c825ad084a6f0851bfc0 Mon Sep 17 00:00:00 2001 From: yigitdot Date: Thu, 3 Sep 2026 21:59:14 +0300 Subject: [PATCH 4/6] docs: drop spec bookkeeping from the protocol pages Co-Authored-By: Claude Opus 5 --- docs/protocol/governance.mdx | 6 +++--- docs/protocol/payments.mdx | 6 +++--- docs/protocol/privacy.mdx | 16 +++++++--------- docs/protocol/reputation.mdx | 4 ++-- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/docs/protocol/governance.mdx b/docs/protocol/governance.mdx index 6c5b7cc..b8ad6a6 100644 --- a/docs/protocol/governance.mdx +++ b/docs/protocol/governance.mdx @@ -39,9 +39,9 @@ Even governance cannot set parameters outside these bounds: | Capacity-curve exponent (α) | 1.2 | 1.0 – 1.8 | | Unbonding window | 14 days | 7 – 60 days | -The three `FeeRouter` shares must sum to exactly 100% on every update. The burn share is the one that also accepts **zero** — the dormant launch configuration, with the split at 90/0/10 until governance activates the buyback sink toward the 60/30/10 steady state. It is therefore either off or at 5% or more; governance cannot leave it nominally on at a negligible share. +The three `FeeRouter` shares must sum to exactly 100% on every update. The burn share is the one that also accepts **zero**, so the buyback sink can be switched off entirely; what it cannot be is nominally on at a negligible share — either off, or at 5% or more. -Bounds are enforced in `require()` checks in the setter functions. A malicious governance proposal that attempts to push the operator revenue share below its floor simply reverts. (The 5% / 15% / 50% slash tiers are themselves governable, within a hardcoded 5%–50% per-offense bound. What governance cannot touch is the 1× / 2× / 4× escalation applied to the _reset period_ on lifetime offense count — that is hardcoded so the anti-gaming curve cannot be flattened. See [slashing](/protocol/slashing).) +Bounds are enforced in `require()` checks in the setter functions. A malicious governance proposal that attempts to push the operator revenue share below its floor simply reverts. (The 5% / 15% / 50% slash tiers are governable too, within a hardcoded 5%–50% per-offense bound — see [slashing](/protocol/slashing).) ## Emergency multisig @@ -77,7 +77,7 @@ The combined ~10-day minimum delay (voting delay + 7-day period + 48-hour timelo ## Why 4% quorum -Low enough that realistic participation can reach it — voting weight tracks active service delivery rather than circulating supply, so a bar calibrated against holdings would quickly exceed engaged voting power. Quorum is not the concentration defense and is not sized as one: the per-operator cap is what bounds any single operator. Note that at their defaults the cap (5%) sits above the quorum bar (4%), so quorum alone does not stop one operator reaching it. The hardcoded safety bounds do the rest — even a captured vote can only move parameters within the safe range. +Low enough that realistic participation can reach it — voting weight tracks active service delivery rather than circulating supply, so a bar calibrated against holdings would quickly exceed engaged voting power. Quorum is not the concentration defense and is not sized as one: the per-operator cap is what bounds any single operator. The hardcoded safety bounds do the rest — even a captured vote can only move parameters within the safe range. ## Regional governance bodies diff --git a/docs/protocol/payments.mdx b/docs/protocol/payments.mdx index 9773b0a..76234a5 100644 --- a/docs/protocol/payments.mdx +++ b/docs/protocol/payments.mdx @@ -59,7 +59,7 @@ claimed = amount + chain_index × chunk_price A preimage is self-proving — nobody derives a deeper one without the seed — so accepting a tick costs one hash, with no signature, no round trip and nothing to persist before the next chunk ships. Signatures are therefore O(1) per transfer rather than one per interval. -The payment quantum is the **payment chunk**: `chunk_bytes` = **1 MiB (1,048,576 bytes)**, a protocol constant that nothing negotiates and no message carries. A released preimage has to be worth the same to payer and node, and a fixed constant removes that disagreement instead of negotiating it away. The protocol's **MB is that same 1,048,576 bytes**, so a chunk costs exactly the node's advertised `rate_per_mb` with no rounding at any rate — `chunk_price` is just that quoted rate, signed into the voucher, and a node rejects any metering voucher whose signed `chunk_price` does not match its own quote. A voucher that seals a lane meters nothing and carries `chunk_price = 0`, so the check does not apply to it. +The payment quantum is the **payment chunk**: `chunk_bytes` = **1 MiB (1,048,576 bytes)**, a protocol constant that nothing negotiates and no message carries. A released preimage has to be worth the same to payer and node, and a fixed constant removes that disagreement instead of negotiating it away. The protocol's **MB is that same 1,048,576 bytes**, so a chunk costs exactly the node's advertised `rate_per_mb` with no rounding at any rate — `chunk_price` is just that quoted rate, signed into the voucher, and a node rejects any voucher whose signed `chunk_price` does not match its own quote. ## Credit window @@ -67,8 +67,8 @@ Payments from one signer to one node out of one pool form a **lane**: an indepen ## Rate discovery -Each node advertises its own per-MB rate, quoted in every signed probe response. Governance sets a **floor**, not a ceiling: a node whose configured rate falls below the current floor raises its quote to the floor and logs a warning rather than refusing to serve, and redemption clamps the byte credit on any voucher priced under it. No governance ceiling caps a node's rate from above — only the wire format's own upper bound — because the buyer sees the signed rate before it pays anything, which is what makes a ceiling unnecessary. A requester rejecting an expensive quote is local policy rather than a protocol rule. A quoted rate is binding for any stream opened inside the [slashing window](/protocol/slashing#the-slashing-window). +Each node advertises its own per-MB rate, quoted in every signed probe response. Governance sets a **floor**, not a ceiling: a node whose configured rate falls below the current floor raises its quote to the floor and logs a warning rather than refusing to serve, and redemption clamps the byte credit on any voucher priced under it. No governance ceiling caps a node's rate from above: the buyer sees the signed rate before it pays anything, which is what makes a ceiling unnecessary. A requester rejecting an expensive quote is local policy rather than a protocol rule. A quoted rate is binding for any stream opened inside the [slashing window](/protocol/slashing#the-slashing-window). ## Minimum deposit -`openPool` enforces a governance-set `minDeposit` on the credited deposit. It is a Sybil knob: the per-pool solvency floor bounds loss within one pool, and an attacker escapes it by fanning out across many pools, so the floor taxes the capital locked in simultaneous pools. Deposits stay fully refundable, and the parameter is bounded at **0 to $100** — there is no floor on the knob itself, and the ceiling keeps governance from pricing small honest buyers out of opening a pool. It **ships dormant at zero**; governance arms it if Sybil pressure warrants. +`openPool` enforces a governance-set `minDeposit` on the credited deposit. It is a Sybil knob: the per-pool solvency floor bounds loss within one pool, and an attacker escapes it by fanning out across many pools, so the floor taxes the capital locked in simultaneous pools. Deposits stay fully refundable, and the parameter is bounded at **0 to $100** — zero leaves it dormant, and the ceiling keeps governance from pricing small honest buyers out of opening a pool. diff --git a/docs/protocol/privacy.mdx b/docs/protocol/privacy.mdx index b82d218..e2f2cc0 100644 --- a/docs/protocol/privacy.mdx +++ b/docs/protocol/privacy.mdx @@ -1,11 +1,11 @@ --- title: Privacy analysis -description: An accountability-first privacy design — a four-tier adversary model, an inventory of privacy surfaces, and the two mitigations committed pre-mainnet. +description: An accountability-first privacy design — a four-tier adversary model, an inventory of privacy surfaces, and the two mitigations it commits to. --- ## Decision -Privacy is treated as an **accountability-first** design: probes are public, on-chain settlement is what makes payment auditable, and the registry is what makes nodes discoverable and slashable. Most privacy surfaces are accepted as inherent. Two mitigations are committed pre-mainnet; deeper techniques are assessed and deferred. +Privacy is treated as an **accountability-first** design: probes are public, on-chain settlement is what makes payment auditable, and the registry is what makes nodes discoverable and slashable. Most privacy surfaces are accepted as inherent. Two are mitigated; deeper techniques are assessed and not adopted. ## Adversary model (four tiers) @@ -16,7 +16,7 @@ Each tier subsumes the one above it. | **Passive observer** | ISP, blockchain analytics firm | QUIC connection metadata (IP pairs, timing, volume), the negotiated ALPN, and everything on-chain | | **Active participant** | Competing CDN, curious node operator, researcher | Plus: probes it receives, stream requests, and the probe responses it collects | | **Infrastructure operator** | RPC provider, relay host | Plus: which registry and blacklist queries a client makes, and IP pairs on relayed connections | -| **Compromised endpoint** | Device theft, malware, warrant | Plus: memory and disk on that one machine — including the network key, unencrypted today | +| **Compromised endpoint** | Device theft, malware, warrant | Plus: memory and disk on that one machine | ## Privacy surfaces @@ -29,16 +29,14 @@ The protocol inventories its distinct privacy surfaces with explicit disposition ## Mitigations -Both are pre-mainnet commitments: - -1. **Client key encryption via platform keychain.** The client's network key sits unencrypted on disk today. Production moves it into the macOS Keychain, Windows Credential Manager, or the Linux Secret Service. (The Ethereum key is already held in an encrypted keystore.) -2. **Operational RPC guidance.** Document that clients and nodes should query through multiple independent RPC providers, or a self-hosted node, so no single vendor sees the whole query pattern. +1. **Client key storage.** Client keys are held in OS-level secure storage — the macOS Keychain, Windows Credential Manager, or the Linux Secret Service — rather than plaintext files on disk. +2. **Operational RPC guidance.** Clients and nodes query through multiple independent RPC providers, or a self-hosted node, so no single vendor sees the whole query pattern. ## Assessed and not adopted - **Client identity rotation.** Rotating the transport identity between sessions is a supported capability, but it is not a privacy commitment: every request carries the pool identifier, and a serving node must resolve the signer's address to be paid at all. That stable payment identity is what an observer correlates on, regardless of node count — so rotation obscures only non-serving probers, whose view is already accepted above. -- **Dummy probes.** Cover-traffic probes hide requests only from nodes that were never going to deliver — the delivering node must learn the hash regardless. Deferred post-mainnet: 3–5× the probe traffic for marginal benefit. -- **Payment pool mixing.** Breaking the on-chain payer↔provider link needs regulatory analysis before any design work. Deferred post-mainnet; pools are long-lived and amortized across many sessions, so they reveal less than per-transfer settlement would. +- **Dummy probes.** Cover-traffic probes hide requests only from nodes that were never going to deliver — the delivering node must learn the hash regardless — 3–5× the probe traffic for marginal benefit. +- **Payment pool mixing.** Breaking the on-chain payer↔provider link needs regulatory analysis before any design work. Pools are also long-lived and amortized across many sessions, so they reveal less than per-transfer settlement would. ## Scope clarifications diff --git a/docs/protocol/reputation.mdx b/docs/protocol/reputation.mdx index 6ddfdd8..3c86f6d 100644 --- a/docs/protocol/reputation.mdx +++ b/docs/protocol/reputation.mdx @@ -28,11 +28,11 @@ interaction_score = 0.4 × speed + 0.4 × correctness + 0.2 × reachability Speed is normalized on a log curve against a node-local reference throughput (default 1 GiB/s) that deliberately **does not saturate** — a materially faster peer earns a materially better score instead of tying with every other fast peer. -A single interaction moves a peer's score by at most **±0.05**, so one bad exchange cannot destroy a good peer's standing. With α = 0.1 the EWMA itself keeps most deltas well under that; the clamp only binds when the gap between the peer's score and the interaction exceeds 0.5. +A single interaction moves a peer's score by at most **±0.05**, so one bad exchange cannot destroy a good peer's standing. ## Why correctness is only one signal among several -Reputation measures service quality, not honesty. Corruption is absorbed at the wire: progressive BLAKE3 verification means a corrupt window yields no voucher, so the node ships garbage unpaid. The reputation hit compounds on top — a corruption event zeros the correctness component, moves the EWMA down by up to the per-interaction clamp, and degrades selection through a quadratic reputation penalty. Lost revenue plus lost traffic make sustained corruption irrational without an on-chain path. +Reputation measures service quality, not honesty. Corruption is absorbed at the wire: progressive BLAKE3 verification means a corrupt window yields no voucher, so the node ships garbage unpaid. The reputation hit compounds on top — a corruption event zeros the correctness component, moves the EWMA down, and degrades selection through a quadratic reputation penalty. Lost revenue plus lost traffic make sustained corruption irrational without an on-chain path. ## Decay toward neutral From 6c29e1b00d78b31f6e01cc96f231c54d7b889c29 Mon Sep 17 00:00:00 2001 From: yigitdot Date: Fri, 4 Sep 2026 04:13:31 +0300 Subject: [PATCH 5/6] docs: name what origin seating grants and mark governable defaults Co-Authored-By: Claude Opus 5 --- docs/overview/architecture.mdx | 10 +++++----- docs/protocol/governance.mdx | 20 ++++++++++---------- docs/protocol/takedown.mdx | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/overview/architecture.mdx b/docs/overview/architecture.mdx index 355d515..e194582 100644 --- a/docs/overview/architecture.mdx +++ b/docs/overview/architecture.mdx @@ -36,11 +36,11 @@ graph TD ## Participant roles -| Role | Operated by | Participates in CDN? | -| ------------------------ | ------------------------------------------------ | ----------------------------------------------------------- | -| **Node (pure cache)** | Independent node operator | Yes — bonds, probes, publishes to the DHT, delivers | -| **Node (origin-backed)** | Content provider _or_ node operator with storage | Yes — same protocol, plus a publisher-seated origin backend | -| **Client** | End-user or application | Pays for bytes; reads the registry but holds no role | +| Role | Operated by | Participates in CDN? | +| ------------------------ | ------------------------------------------------ | ----------------------------------------------------------------------------- | +| **Node (pure cache)** | Independent node operator | Yes — bonds, probes, publishes to the DHT, delivers | +| **Node (origin-backed)** | Content provider _or_ node operator with storage | Yes — same protocol, plus authorization to serve as an origin for a namespace | +| **Client** | End-user or application | Pays for bytes; reads the registry but holds no role | See [Participants](/overview/participants) for a deeper breakdown per role. diff --git a/docs/protocol/governance.mdx b/docs/protocol/governance.mdx index b8ad6a6..681ebd5 100644 --- a/docs/protocol/governance.mdx +++ b/docs/protocol/governance.mdx @@ -28,16 +28,16 @@ The phase ends when the multisig judges the operator set broad and diverse enoug Even governance cannot set parameters outside these bounds: -| Parameter | Default | Bounds | -| ----------------------------------------- | -------- | ------------ | -| Operator revenue share (`FeeRouter` base) | 60% | 40% – 90% | -| Burn share (`FeeRouter`) | 30% | 5% – 50% | -| Treasury share (`FeeRouter`) | 10% | 0% – 30% | -| Redemption grace window | 48 h | 48 h – 72 h | -| Per-operator voting cap | 5% | 1% – 25% | -| Trailing vote window | 13 weeks | 4 – 26 weeks | -| Capacity-curve exponent (α) | 1.2 | 1.0 – 1.8 | -| Unbonding window | 14 days | 7 – 60 days | +| Parameter | Default | Bounds | +| ----------------------------------------- | ---------------- | -------------------- | +| Operator revenue share (`FeeRouter` base) | 60% | 40% – 90% | +| Burn share (`FeeRouter`) | 30% (0% dormant) | 5% – 50% when active | +| Treasury share (`FeeRouter`) | 10% | 0% – 30% | +| Redemption grace window | 48 h | 48 h – 72 h | +| Per-operator voting cap | 5% | 1% – 25% | +| Trailing vote window | 13 weeks | 4 – 26 weeks | +| Capacity-curve exponent (α) | 1.2 | 1.0 – 1.8 | +| Unbonding window | 14 days | 7 – 60 days | The three `FeeRouter` shares must sum to exactly 100% on every update. The burn share is the one that also accepts **zero**, so the buyback sink can be switched off entirely; what it cannot be is nominally on at a negligible share — either off, or at 5% or more. diff --git a/docs/protocol/takedown.mdx b/docs/protocol/takedown.mdx index e5c5000..0b40821 100644 --- a/docs/protocol/takedown.mdx +++ b/docs/protocol/takedown.mdx @@ -70,4 +70,4 @@ Each node maintains a **local denylist** for direct legal notices (e.g., a DMCA ## Slash schedule -Blacklist violations use an escalating schedule: **5% / 15% / 50%** for first / second / third offense (capped at 50% per offense). Offenses accumulate over the operator's lifetime; a node whose bond falls below 50% of the minimum is auto-ejected. +Blacklist violations use an escalating schedule — **5% / 15% / 50%** by default for first / second / third offense, capped at 50% per offense. Offenses accumulate over the operator's lifetime; a node whose bond falls below 50% of the minimum is auto-ejected. From 07849c73923d59d8529872d12819131c3f481719 Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Thu, 17 Sep 2026 17:42:10 +0300 Subject: [PATCH 6/6] docs: correct slash ladder, challenge bond, and four other claims Co-Authored-By: Claude Opus 4.8 --- docs/protocol/governance.mdx | 2 +- docs/protocol/payments.mdx | 2 +- docs/protocol/privacy.mdx | 2 +- docs/protocol/reputation.mdx | 2 +- docs/protocol/slashing.mdx | 2 +- docs/protocol/takedown.mdx | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/protocol/governance.mdx b/docs/protocol/governance.mdx index 681ebd5..42d2950 100644 --- a/docs/protocol/governance.mdx +++ b/docs/protocol/governance.mdx @@ -41,7 +41,7 @@ Even governance cannot set parameters outside these bounds: The three `FeeRouter` shares must sum to exactly 100% on every update. The burn share is the one that also accepts **zero**, so the buyback sink can be switched off entirely; what it cannot be is nominally on at a negligible share — either off, or at 5% or more. -Bounds are enforced in `require()` checks in the setter functions. A malicious governance proposal that attempts to push the operator revenue share below its floor simply reverts. (The 5% / 15% / 50% slash tiers are governable too, within a hardcoded 5%–50% per-offense bound — see [slashing](/protocol/slashing).) +Bounds are enforced in `require()` checks in the setter functions. A malicious governance proposal that attempts to push the operator revenue share below its floor simply reverts. (The 5% / 15% / 50% slash ladder is a fixed constant in the bond contract, not a governable parameter — see [slashing](/protocol/slashing).) ## Emergency multisig diff --git a/docs/protocol/payments.mdx b/docs/protocol/payments.mdx index 76234a5..d92f5be 100644 --- a/docs/protocol/payments.mdx +++ b/docs/protocol/payments.mdx @@ -59,7 +59,7 @@ claimed = amount + chain_index × chunk_price A preimage is self-proving — nobody derives a deeper one without the seed — so accepting a tick costs one hash, with no signature, no round trip and nothing to persist before the next chunk ships. Signatures are therefore O(1) per transfer rather than one per interval. -The payment quantum is the **payment chunk**: `chunk_bytes` = **1 MiB (1,048,576 bytes)**, a protocol constant that nothing negotiates and no message carries. A released preimage has to be worth the same to payer and node, and a fixed constant removes that disagreement instead of negotiating it away. The protocol's **MB is that same 1,048,576 bytes**, so a chunk costs exactly the node's advertised `rate_per_mb` with no rounding at any rate — `chunk_price` is just that quoted rate, signed into the voucher, and a node rejects any voucher whose signed `chunk_price` does not match its own quote. +The payment quantum is the **payment chunk**: `chunk_bytes` = **1 MiB (1,048,576 bytes)**, a protocol constant that nothing negotiates and no message carries. A released preimage has to be worth the same to payer and node, and a fixed constant removes that disagreement instead of negotiating it away. The protocol's **MB is that same 1,048,576 bytes**, so a chunk costs exactly the node's advertised `rate_per_mb` with no rounding at any rate — `chunk_price` is just that quoted rate, signed into the voucher, and a node rejects any _metering_ voucher whose signed `chunk_price` does not match its own quote. A sealed voucher — the cooperative-close shape — meters no chunk and carries `chunk_price = 0`, so the equality check does not apply to it. ## Credit window diff --git a/docs/protocol/privacy.mdx b/docs/protocol/privacy.mdx index e2f2cc0..d68995a 100644 --- a/docs/protocol/privacy.mdx +++ b/docs/protocol/privacy.mdx @@ -29,7 +29,7 @@ The protocol inventories its distinct privacy surfaces with explicit disposition ## Mitigations -1. **Client key storage.** Client keys are held in OS-level secure storage — the macOS Keychain, Windows Credential Manager, or the Linux Secret Service — rather than plaintext files on disk. +1. **Client key storage.** The design keeps client keys in OS-level secure storage — the macOS Keychain, Windows Credential Manager, or the Linux Secret Service — rather than plaintext files on disk. 2. **Operational RPC guidance.** Clients and nodes query through multiple independent RPC providers, or a self-hosted node, so no single vendor sees the whole query pattern. ## Assessed and not adopted diff --git a/docs/protocol/reputation.mdx b/docs/protocol/reputation.mdx index 3c86f6d..21cac77 100644 --- a/docs/protocol/reputation.mdx +++ b/docs/protocol/reputation.mdx @@ -26,7 +26,7 @@ interaction_score = 0.4 × speed + 0.4 × correctness + 0.2 × reachability | Data correctness (BLAKE3 verified) | 0.0 or 1.0 | 40% | | Connection success (reachable?) | 0.0 or 1.0 | 20% | -Speed is normalized on a log curve against a node-local reference throughput (default 1 GiB/s) that deliberately **does not saturate** — a materially faster peer earns a materially better score instead of tying with every other fast peer. +Speed is normalized on a log curve against a node-local reference throughput (default 1 GiB/s), reaching its maximum at that reference. Below it the curve keeps climbing rather than **flattening at a low baseline** — a materially faster peer earns a materially better score instead of tying with every other fast peer. A single interaction moves a peer's score by at most **±0.05**, so one bad exchange cannot destroy a good peer's standing. diff --git a/docs/protocol/slashing.mdx b/docs/protocol/slashing.mdx index 47b8b11..9a22595 100644 --- a/docs/protocol/slashing.mdx +++ b/docs/protocol/slashing.mdx @@ -39,4 +39,4 @@ The bond reduction is immediate, but the slashed TOKEN sits in escrow rather tha ## Challenger incentives -Submitting evidence requires a **100 TOKEN** bond, transferred at the reveal. A reveal that fails on-chain verification simply reverts — the challenger only loses gas, and the bond is not transferred. A challenge that passes always slashes the node and the bond is returned. At finality (≤ 30 days with no appeal, later if one is filed) the escrowed bond is split **50% to the challenger / 50% burned**, unless the operator's appeal is granted, in which case the operator is refunded and no challenger reward is paid. +Submitting evidence requires a challenge bond — **100 TOKEN** by default, governance-set within a 1–1,000 TOKEN band — transferred at the reveal. A reveal that fails on-chain verification simply reverts — the challenger only loses gas, and the bond is not transferred. A challenge that passes always slashes the node and the bond is returned. At finality (≤ 30 days with no appeal, later if one is filed) the escrowed bond is split **50% to the challenger / 50% burned**, unless the operator's appeal is granted, in which case the operator is refunded and no challenger reward is paid. diff --git a/docs/protocol/takedown.mdx b/docs/protocol/takedown.mdx index 0b40821..d5ac716 100644 --- a/docs/protocol/takedown.mdx +++ b/docs/protocol/takedown.mdx @@ -29,7 +29,7 @@ Off-chain distribution fails all three. On-chain entries with governance gating | Standard governance | Governor | 24 h after vote execution (compliance window) | None (governance can remove) | | Regional | Regional governance body | 24 h | None | | Emergency | 3-of-5 multisig | Immediately (with 2 h slash grace) | 14 days | -| Emergency (CSAM / terrorist) | 3-of-5 multisig | Immediately | 90 days | +| Emergency (CSAM / terrorist) | 3-of-5 multisig | Immediately (with 2 h slash grace) | 90 days | Emergency entries **auto-expire unless ratified by governance** before the deadline — preventing the multisig from maintaining a permanent de-facto blacklist without oversight. Expiry needs no transaction: the entry simply stops being enforceable, and anyone may then clean it out of storage permissionlessly. @@ -70,4 +70,4 @@ Each node maintains a **local denylist** for direct legal notices (e.g., a DMCA ## Slash schedule -Blacklist violations use an escalating schedule — **5% / 15% / 50%** by default for first / second / third offense, capped at 50% per offense. Offenses accumulate over the operator's lifetime; a node whose bond falls below 50% of the minimum is auto-ejected. +Blacklist violations use a fixed escalating ladder — **5% / 15% / 50%** for first / second / third offense, capped at 50% per offense. Offenses accumulate over the operator's lifetime; a node whose bond falls below 50% of the minimum is auto-ejected.