SWIP-061: BPS multihop — FCFS multicast tree - #105
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…PO_MIN Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per SWIP-65 (PR #106): a delivery gap on a self-indexed cohort is detectable and recoverable from storage, so the contract is satisfied by deliver-or-recover. The second parent becomes a per-subscriber latency/cost choice - single-parented nodes were already well-formed, and a single-parented relay endangers only itself. Conformance items 5-6 bind only nodes that maintain a second parent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Amendment pushed (1ca57ae), companion to SWIP-65 (#106):
🤖 Generated with Claude Code |
There was a problem hiding this comment.
Pull request overview
This PR introduces SWIP-61, extending the BPS (Broadcast Pub/Sub) family beyond the SWIP-60 single-hop audience ceiling by specifying a multihop, FCFS multicast tree join/control plane with subtree probing and dual-parent attachment for churn masking.
Changes:
- Adds the SWIP-61 specification document describing probe-based joining, two-parent resilience, and make-before-break reparenting.
- Adds the SWIP-61 wire specification (
bps.proto) definingReparent,Probe,Candidates, andAck.candidates.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
SWIPs/swip-61.md |
New SWIP-61 spec describing multihop join/probe behavior, dual-parent resilience, and reparenting semantics. |
SWIPs/assets/swip-61/bps.proto |
Proto definitions for SWIP-61 control frames and the Ack extension carrying attachment candidates. |
Suppressed comments (3)
SWIPs/swip-61.md:381
- Inconsistent acronym casing:
dCUTrhere should match theDCUtRspelling used earlier in the document.
dCUTr: [bee#5355](https://github.com/ethersphere/bee/issues/5355)
SWIPs/assets/swip-61/bps.proto:84
- The
Subscribecomment is broker-specific, but in multihop a joiner canSubscribeto any known cohort node (broker or relay). Keeping the comment broker-only is misleading for implementers.
// Joiner -> broker: names the topic — nothing more. Subscribers carry no
// cohort metadata; auth is present iff the joiner publishes (publishers
// connect directly to the broker).
SWIPs/assets/swip-61/bps.proto:96
- The
Ackcomment is broker-specific, but in multihop relays also answerSubscribewithAck. Adjust wording to cover any cohort node responding on the stream.
// Broker -> peer, answering Open or Subscribe. The echoed CohortSpec lets a
// subscriber verify every message end-to-end against the topic binding.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // Stream establishment, stream name "pubsub/1.0.0" — one stream per (peer, topic). | ||
| // The first message on a fresh stream is Open (fixes a new cohort) or | ||
| // Subscribe (joins an existing one); the broker answers with Ack. | ||
| // --------------------------------------------------------------------------- |
| // Spec: swip-bps-singlehop.md (SWIP-60, base protocol) and | ||
| // swip-bps-multihop.md (SWIP-61, multihop control plane). |
| Two cases: | ||
|
|
||
| - **graceful leave** — a departing relay `Reparent`s each child toward a replacement | ||
| (its own parents, or spread over its children **(?)**) before closing; the child's |
…pth 1 Realigns SWIP-61 with SWIP-60 rev 4 and lifts the invariant that confined publishers to the root. ## Two amendments to SWIP-60, not one 1. The capacity rule (unchanged in substance): a relaying node at capacity answers Ack(FULL, candidates). 2. NEW — Publish travels rootward, forwarded hop by hop to the broker, which validates, dedups and broadcasts it back down. SWIP-60's direct attachment of publishers was a consequence of depth = 1, not an invariant. Multihop now restructures both directions, which is what an everyone-publishes cohort (SWIP-60's ALL, and any GRANTED cohort larger than one node's capacity) needs in order to exist at all. Open still goes to the broker — opening a cohort IS contacting the root — but its admin is thereafter an ordinary publisher and may sit at any depth. New section "Publishing from depth" covers: validation authoritative at the root (dedup is a statement about what the cohort has seen, and only the root sees everything) while relays MAY and SHOULD run the stateless checks early; publishing up BOTH parents, so a single relay cannot silently swallow a publish — the upstream twin of what two feeds buy downstream; and depth priced rather than bounded. Revocation across a tree: the roster update is an ordinary broadcast, so enforcement lands at the revoked publisher's parents, and the revokee learns of its own revocation from the same broadcast at the same time — which is what makes SWIP-60's second phase fair at any depth. ## The depth-bound argument, re-anchored It previously rested on the closed cohort being "structurally singlehop". That anchor is gone twice over: `closed` no longer exists in SWIP-60, and publishers no longer attach to the root. NO configuration requires depth 1. The argument now stands on the price alone — and is stronger, since depth-scaled upstream pricing bills a publisher for its depth, the case where the incentive bites hardest. ## What a relay owes a joiner SWIP-60 rev 4's Ack carries the echoed CohortSpec, the admin-signed genesis SOC and the latest service SOC with its index. A relay MUST pass on all three unchanged: depth must cost a subscriber nothing in what it can verify, so a leaf ten hops out checks cohort and roster against the admin's key exactly as the broker's own child does. Extra intermediaries add withholding points and no forging points; a relay serving a stale service SOC is withholding, detected as an index gap. `spectators: false` cohorts work at depth for the same reason — any node answering a Subscribe holds the roster. ## Wire bps.proto regenerated from SWIP-60 rev 4 rather than patched, so the two files no longer drift. Ack.candidates takes field 6, NOT 3 — fields 3-5 are SWIP-60's service SOCs, which the old draft would have collided with. ## Review items - Copilot: front matter split into `type: Standards Track` + `category: Networking`; proto header pointed at swip-60.md / swip-61.md instead of the nonexistent swip-bps-*.md; establishment comments corrected to say any cohort node answers Subscribe, not only the broker; DCUtR casing normalised. - The `(?)` on graceful leave is resolved: a departing relay reparents children to its own parents — known-live, known-compatible, one level shallower. Handing children to one another would deepen the tree and has no answer for the first child. - Both mermaid diagrams verified to parse (mermaid 11), after the semicolon defect found in SWIP-60's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Rev 2 pushed ( Two amendments to SWIP-60 now, not oneThe capacity rule is unchanged. The new one: SWIP-60's direct attachment of publishers was a consequence of depth = 1, not an invariant — so multihop now restructures both directions rather than the audience side only. That is what an everyone-publishes cohort needs in order to exist at all: SWIP-60's
A new section, Publishing from depth, settles three things:
Revocation across a tree: the roster update is an ordinary broadcast, so enforcement lands at the revoked publisher's parents — they are the nodes accepting its The "why no depth bound" argument, re-anchoredIt used to rest on the closed cohort being structurally singlehop. That anchor is gone twice over: The argument now stands on the price alone, and is stronger for it — with What a relay owes a joinerSWIP-60 rev 4's Depth must cost a subscriber nothing in what it can verify. A leaf ten hops out checks the cohort and the roster against the admin's key exactly as the broker's own child does, so extra intermediaries add withholding points and no forging points. A relay serving a stale service SOC is withholding, which its subscriber detects as an index gap. Wire — one collision caught
Review items clearedThanks @copilot — all taken:
The Both mermaid diagrams are verified to parse under mermaid 11. Worth noting for anyone editing SWIP diagrams: a semicolon in note text silently breaks mermaid — it is a statement separator, so 🤖 Generated with Claude Code |
Extends SWIP-60 (#104): removes the singlehop audience ceiling. A full node at capacity answers
Subscribeby probing its own subtree over existing streams and returning the two shallowest attachment points — the joiner attaches to both, so every node keeps two parents (typically sisters): churn is masked, not suffered, self-healing is re-running the join, and withholding shows by comparing feeds. Join is ideally a two-step process regardless of tree size.Key properties: forwarders are subscribers (accepting your first child = becoming a relay); no standing control state — idle cohorts are silent, probes carry depth in the frame itself; no depth bound (per-hop pricing steers joins rootward; closed cohorts are structurally singlehop);
CohortSpecuntouched; three control frames (Reparent,Probe,Candidates) fill the reservedBroadcastrange; single newAckfield. Unmodified SWIP-60 clients attach as conformant (single-parented) leaves.Deliberately out of scope (own SWIPs): bandwidth incentives, message history (pre-subscription delivery), broker discovery (SWIP-59, #103), NAT traversal (transport: circuit relay + DCUtR).
Wire:
assets/swip-61/bps.proto— the SWIP-60 proto with the multihop control plane filled in. Implementation groundwork: bee ethersphere/bee#5435.🤖 Generated with Claude Code