Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pnpm check:og-image # postbuild social-card guard over ./out (runs inside `pnp
- `components/site/` — page sections composed by `app/page.tsx` (Hero, Compare, Method, Faq, Contact, …) plus chrome (`Chrome`, `Footer`, `ScrollReveal`, …). Section components are named after their section `id` (e.g. `Contact.tsx` for `id="contact"`); `Hero` is the idiomatic exception for the top `id="intro"` section.
- `components/ui/` — low-level primitives (Frame, SectionHeader, Prose, Figure, …).
- `lib/` — shared helpers (`links.ts`, `blog.ts`, `faq.ts`, `legal.ts`, `jsonld.tsx`, `schema.ts`, …). `lib/schema.ts` holds the site-level JSON-LD nodes; the per-post `BlogPosting` node is `blogPostingNode` in `lib/blog.ts` because it derives from a post rather than from the site — `blogNode` nests it and `app/blog/[slug]/page.tsx` emits it top-level, so they cannot diverge under the same `@id`.
- `lib/copy.ts` — the site's prose, as plain data. New or edited homepage copy goes here, not inline in a component: the comparison table maps over `COMPARE_ROWS` and `app/llms-full.txt/route.ts` serialises the same strings as markdown, so copy inlined in JSX silently drifts from the mirror. This is not hypothetical — the hero and method stat strips were inline `<Figure>` attributes restated by hand in the mirror, and had already drifted. Values as well as sentences: `HERO_FIGURES`, `STACK`, `METHOD_FIGURES`, `DEMO_CAPTIONS`. Render prose through `highlightBrand` (`components/ui/brand.tsx`) wherever the sentence names the product.
- `lib/copy.ts` — the site's prose, as plain data. New or edited homepage copy goes here, not inline in a component: the comparison table maps over `COMPARE_ROWS` and `app/llms-full.txt/route.ts` serialises the same strings as markdown, so copy inlined in JSX silently drifts from the mirror. This is not hypothetical — the hero and method stat strips were inline `<Figure>` attributes restated by hand in the mirror, and had already drifted. Values as well as sentences: `HERO_FIGURES`, `STACK`, `DEMO_CAPTIONS`. Render prose through `highlightBrand` (`components/ui/brand.tsx`) wherever the sentence names the product.
- `test-utils/` — test-only helpers (`react-tree.ts` walks the element tree a server component returns). Nothing under `app/` or `components/` imports from here.
- `scripts/` — `check-out.mjs`, the postbuild assertions `pnpm build` runs against `./out`.
- `content/blog/` — MDX posts loaded by `lib/blog.ts` and rendered via `app/blog/[slug]/page.tsx`.
Expand Down
2 changes: 1 addition & 1 deletion content/blog/06-show-me-the-money.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ That is the reference month for a healthy decdn node at the launch-fleet target:

The reference node serves 100 TB in a month. It runs on a low-cost 1 Gbps-class setup: a Hetzner CPX22 with bandwidth overage, or an equivalent unmetered dedicated tier once traffic makes metered bandwidth a bad trade. Around 100 TB/month, those curves converge. Below that, a cheap VPS can work. Above that, a flat bandwidth port starts to matter.

The rate is **$0.01/GB**. That is the public target rate, not a protocol-enforced price. Nodes advertise their own rates; clients choose among price, latency, and reputation. The model uses $0.01/GB because it is the launch reference price: cheap enough to be a real CDN alternative, high enough for efficient operators to clear costs.
The rate is **$0.01/GB**. That is the public target rate, not a protocol-enforced price. Nodes set their own rates; clients rank nodes by measured latency and can refuse any rate above a ceiling they set. The model uses $0.01/GB because it is the launch reference price: cheap enough to be a real CDN alternative, high enough for efficient operators to clear costs.

The traffic mix is:

Expand Down
4 changes: 2 additions & 2 deletions docs/overview/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ 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 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 into one selection score; the lowest score wins, so a cheap or nearby node can outrank a better-reputed one.
2. **Discovery** — the client takes candidate nodes from its own peer store or the registry; it runs no DHT lookup. A parallel probe confirms which of them will actually serve the wanted range, and how fast they answer. A fresh peer store lets the client skip the probe entirely.
3. **Selection** — the client ranks holders by measured round-trip time alone. Price is not a rank key: the client pays the rate the node signs and can refuse a quote above a ceiling of its own. The client keeps no reputation score.
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, 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.
Expand Down
2 changes: 1 addition & 1 deletion docs/overview/glossary.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ description: Definitions for the core deCDN terms — bao verified streaming, bl
| **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. |
| **Selection score** | The ranking a node uses to pick an upstream holder on a cache miss, combining advertised price, observed latency, and its local reputation score. Lower is better. Clients do not use it: they rank by measured round-trip time alone. |
| **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. |
Expand Down
6 changes: 3 additions & 3 deletions docs/overview/how-it-works.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ sequenceDiagram
N->>P: Redeem (voucher + preimage)
```

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.
The client picks the holder with the lowest measured round-trip time. It pays the rate the node signs, and can refuse a quote above a ceiling of its own; it keeps no reputation score. 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

Expand Down Expand Up @@ -51,9 +51,9 @@ A node may also simply decline. Refusing is not a protocol violation and is not

## Multi-source parallel fetch

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).
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 measured round-trip time, 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 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 follows **coverage**: 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 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.

Expand Down
9 changes: 6 additions & 3 deletions docs/protocol/network.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,20 @@ When a lookup returns no providers for a request that names a namespace, the nam

## Selection

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.
Clients and nodes rank candidates differently.

- **Clients** take candidates from their own peer store or the registry, not the DHT, and rank them by measured round-trip time alone. Price is not a rank key: the client pays the rate the node signs and can refuse a quote above a ceiling of its own. A client keeps no reputation score; it only sets aside, for a few minutes, a node that just failed it.
- **Nodes** pulling on a cache miss rank holders by a unified score combining advertised price, observed latency, and their local reputation score, 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.

## Regions

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.
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 pulling node 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.

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.
Regions inform selection rather than partition the mesh: a client in Frankfurt uses its region only to shortlist which candidates to probe, and prefers nearby ones because they answer faster, not because it sees a different peer set.

## NAT traversal

Expand Down
2 changes: 1 addition & 1 deletion docs/protocol/reputation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Each node scores its peers 0.0–1.0 from its own delivery outcomes

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.

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.
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 a pulling node's choice among otherwise-eligible upstream holders on a cache miss. Clients keep no reputation score; they rank nodes by measured round-trip time alone ([network](/protocol/network#selection)).

## Scoring

Expand Down
8 changes: 4 additions & 4 deletions lib/copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const HERO_HEADLINE = [
] as const;

export const HERO_LEAD =
"The first bytes of a 14-gigabyte file posted in Berlin reach a client in Tokyo in under a second — streamed from three peers at once, every chunk verified with BLAKE3, paid for per megabyte in USDC. The code is open. The network is open. The price is posted.";
"A 14-gigabyte file posted in Berlin streams to a client in Tokyo from several nodes at once, every chunk verified with BLAKE3, paid for per megabyte in USDC. The code is open. The network is open. The price is posted.";

/** A label/value pair rendered by `components/ui/Figure`. Named `FigureCopy`
* rather than `Figure` so a call site can import both the copy and the
Expand Down Expand Up @@ -111,7 +111,7 @@ export const COMPARE_ROWS = [
{
axis: "failure",
traditional: "PoP dies, region 503s",
decdn: "peer drops, stream continues",
decdn: "node drops, stream continues",
},
{
axis: "scaling",
Expand All @@ -135,12 +135,12 @@ export const METHOD_STEPS: readonly MethodStep[] = [
{
n: "01",
word: "probe",
body: "In a single handshake, the client asks nearby peers who has the file. Peers answer with what they've cached, their rate per megabyte, and how fast they can serve — the round trip averages under 100 milliseconds. The client ranks the answers by price, latency, and reputation; the best-priced, fastest, most-reputable peer wins, or several win in parallel for a large file.",
body: "The client draws candidate nodes from the on-chain CapacityBond registry, or from its own peer store of nodes it has measured before — if that store is fresh, it skips the probe entirely. Otherwise it probes a shortlist of candidates in parallel for who holds the file and how fast they answer. It ranks holders by measured round-trip time alone. Price is not a rank key: the client pays the rate the node signs, and can cap it with a ceiling of its own. No reputation score is kept.",
},
{
n: "02",
word: "swarm",
body: "Bytes flow directly from the chosen node; for files over ten gigabytes the client opens parallel streams to several peers at once and aggregates their throughput — a 1 Gbps origin turns into multi-gigabit delivery to the client. Every chunk is verified against the BLAKE3 tree hash the instant it lands; tampered bytes trigger immediate disconnect and a fraud proof against the node's stake. Trust no node — verify every byte.",
body: "Bytes stream directly from the chosen node. For files over 64 MiB with at least two holders, the client splits the fetch across several nodes — one per operator — and fills its downlink from all of them at once. Every chunk is verified against the BLAKE3 tree hash as it lands. Bytes that fail verification never get a voucher, so the node that sent them is paid nothing for that range, and the client refetches it from another node. Trust no node — verify every byte.",
},
{
n: "03",
Expand Down
Loading
Loading