Skip to content

Add SWIP-draft: BPS-lite — single-publisher brokered broadcast - #111

Open
acud wants to merge 3 commits into
masterfrom
swip-bps-lite
Open

acud wants to merge 3 commits into
masterfrom
swip-bps-lite

Conversation

@acud

@acud acud commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

A standalone single-publisher profile of the design space SWIP-60 (PR #104) covers: one broker, one publisher, N subscribers, one hop.

A cohort is a topic and a topic is a SOC address, so authorship needs no credential, no publisher list and no authenticated handshake — the ANCHOR address check re-establishes it from the signature on every message. The publisher role is bound to the opening stream and transfers on re-Open; the cohort's lifetime is that stream, which removes orphan cohorts and bounds Open as an allocation primitive.

Specified standalone rather than as a profile of SWIP-60, with the protobuf inline and no compatibility claimed in either direction. It answers three questions SWIP-60 leaves open (dedup horizon, cohort lifetime, Open as an unbounded allocation primitive) and drops closed rather than restating a confidentiality claim an unauthenticated handshake cannot support.

The two specs share the pubsub/1.0.0 protocol id, so the handshake carries the compatibility story: SWIP-60's field numbers are reserved, not reused, which is what lets a BPS-lite broker refuse a full-spec Open outright instead of silently decoding it with fields dropped.

Note the one divergence that can fail silently for implementers: BPS-lite's ANCHOR applies its address constraint unconditionally, where SWIP-60 relaxes it under an explicit publisher regime.

A standalone single-publisher profile of the design space SWIP-60 (PR #104)
covers: one broker, one publisher, N subscribers, one hop.

A cohort is a topic and a topic is a SOC address, so authorship needs no
credential, no publisher list and no authenticated handshake — the ANCHOR
address check re-establishes it from the signature on every message. The
publisher role is bound to the opening stream and transfers on re-Open; the
cohort's lifetime is that stream, which removes orphan cohorts and bounds Open
as an allocation primitive.

Specified standalone rather than as a profile of SWIP-60, with the protobuf
inline and no compatibility claimed in either direction. It answers three
questions SWIP-60 leaves open (dedup horizon, cohort lifetime, Open as an
unbounded allocation primitive) and drops `closed` rather than restating a
confidentiality claim an unauthenticated handshake cannot support.

The two specs share the pubsub/1.0.0 protocol id, so the handshake carries the
compatibility story: SWIP-60's field numbers are reserved, not reused, which is
what lets a BPS-lite broker refuse a full-spec Open outright instead of
silently decoding it with fields dropped.

Note the one divergence that can fail silently for implementers: BPS-lite's
ANCHOR applies its address constraint unconditionally, where SWIP-60 relaxes it
under an explicit publisher regime.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E99KGsCW7dyKudy2WbpRco

@zelig zelig left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

review is not yet finished. submitting it so far nonetheless for discussion.

Comment thread SWIPs/swip-draft_bps-lite.md Outdated
## Simple Summary

One broker, one publisher, many subscribers, one hop. A publisher opens a topic on a
broker and pushes single-owner chunks; everyone else connects and reads. Nothing else.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A publisher must explicitly connect to the broker and has the exlusuve right to publish messages. Just like with all subsequent modes, messages are of a shape of single owner chunks. In this specific case these chunks must be higher-index versions (updates) of a feed with the topic (the cohort topic bound to the feed). All other joiners of the cohort are subscribers.

Comment thread SWIPs/swip-draft_bps-lite.md Outdated

## Abstract

BPS-lite is a real-time broadcast protocol in which a cohort is a topic and a topic is

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BPS-lite is a real-time broadcast protocol a kind of push notifications on a channel that participants can subscribe to and a restricted set of participants can publish on.
Each channel defines its own cohort (a set of nodes), which are connected through a unique central broadcaster node, the broker. In BPS-lite, each node in the cohort will be directly connected to the broker, hence the moniker single-hop.

Comment thread SWIPs/swip-draft_bps-lite.md Outdated
Comment on lines +25 to +31
a single-owner chunk address. The peer that opens a cohort is its publisher for as long
as that stream lives; every other peer is a read-only subscriber. Authorship needs no
credential, no publisher list and no handshake authentication: under the `ANCHOR`
binding the topic already names the only key that can produce a chunk hashing to it, so
authorship is re-established from the signature on every message. The cohort's lifetime
is its publisher's stream, which removes orphan cohorts and bounds `Open` as an
allocation primitive.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure i understand this (allocation primitive) but yes Open (open should be interpreted in terms of the connecting node, open connection to the channel, maybe we should call it Join) can create the channel for the broker.

Each channel is uniquely identified by a cohort specification. Turning it around, the cohort specification must contain all information that can distinguish channels, and exclude stuff that can change without changing the integrity of the cohort (secondary publishers).

Open message includes the cohort spec, if someone sends an Open message to the broker node, then the cohort spec is compared to existing open channels. IF one matches, then the candidate is trying to join if not then the channel is opened.

Comment thread SWIPs/swip-draft_bps-lite.md Outdated
is its publisher's stream, which removes orphan cohorts and bounds `Open` as an
allocation primitive.

It is a deliberate subset of the design space [SWIP-60][swip60] covers, specified as

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subset it really is , and should not deviate though.

Comment thread SWIPs/swip-draft_bps-lite.md Outdated

It is a deliberate subset of the design space [SWIP-60][swip60] covers, specified as
its own document rather than as a profile: the wire format below is complete, and where
BPS-lite differs from SWIP-60 — most importantly in making `ANCHOR`'s address

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, so the ANCHOR first of should not be the first of all :) IT must be feed
What is relevant here is single explicit publisher in which case the messages are (supposed to be the ordered updates of a feed.

Comment thread SWIPs/swip-draft_bps-lite.md Outdated

### Handshake

Publisher-first. The publisher fixes the cohort; subscribers join it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, anyone (so a subscriber also) can open, join a channel.
Note that if the joiners are not admins, they cannot publish (if they do, protocol violation), so the channel only has minimal resource demand, in fact only an entry in the broker nodes channels map.
There must be an inactivity deadline, so there is no trolling with opening a lot of channels and squat the broker node capacity with bogus idle cohorts.
If the subsciber has no active subscriptions, it can be disconnected.
Similarly, there must also be a maximum channel number per node connection so that a node cannot overwhelm the host broker with newly joined bogus channels while keeping a bonding legit one open.

Comment thread SWIPs/swip-draft_bps-lite.md Outdated

A `Publish` is accepted iff, in order:

1. it arrived on the stream that opened the cohort;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A published SOC received by the broker is valid if
-!!! the ID of the SOC is an int > feed cursor

  • suubstituting the ID with H(topic|int) the msg validates as a SOC
  • and its owner is one of the publishers (in our case of singleton, the admin).

Comment thread SWIPs/swip-draft_bps-lite.md Outdated
4. the wrapped CAC's BMT root matches its payload;
5. the CAC address is not in the cohort's dedup window.

Failures 1–4 are protocol violations: the message is dropped, counted per peer, and

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all failures are protocol violations and result in stream cleanup,
disconnect and blocklisting (ONLY if you are the broker).

Comment thread SWIPs/swip-draft_bps-lite.md Outdated
already failed.

On acceptance the broker enqueues a `Broadcast` on every subscriber stream in the
cohort, and on the publisher's own stream if it requested loopback.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no loopback ever

Comment thread SWIPs/swip-draft_bps-lite.md Outdated
orphan cohorts and any reclamation policy, and it bounds `Open` as an allocation
primitive: a peer can hold only as many live cohorts as it holds open streams.

### Dedup

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no dedup , see SWIP-65 how we handle missed items

zelig and others added 2 commits September 22, 2026 16:12
…blisher over a feed

Rewrite of the first draft after review. BPS-lite is now a strict subset/base of
SWIP-60 rather than a divergent profile: the live-stream configuration with its wire
reduced to what it needs.

- cohort spec = {topic, FEED_TOPIC, admin}, and the spec is the cohort's identity
- one handshake frame, Join{CohortSpec, Auth?}: create-or-attach, anyone may do either;
  no Hello/Open/Subscribe, no UNKNOWN_TOPIC
- Auth binds the identity to the stream, not the connection; a stream whose Auth
  recovers to admin is a publisher stream, every other stream is read-only
- Ack{status} only: OK / FULL / REJECTED
- one Message{soc} frame both directions; the chunk as opaque chunk data with the bare
  index in the id slot (SWIP-65 carriage)
- validation: publisher stream, index > cursor, valid SOC under keccak256(topic||index),
  owner == admin; no dedup window, gaps allowed, retransmits counted not punished
- no loopback: deliveries to subscriber streams only
- lifetime by inactivity only — no service messages, no end-of-stream
- broker bounds: subscribers/cohort, cohorts/broker, cohorts/peer connection,
  inactivity deadline, per-stream queue
- no history, no bandwidth incentive, no Bee API, one hop, one mode; implementation
  section removed
- "Relation to SWIP-60" lists the amendments SWIP-60 needs to extend this wire
  (Join, cohorts keyed by spec, no GENESIS, regime reduced to ALL, spectators
  polarity, opaque chunk) and the one-line SWIP-65 exception
- number assigned: 74; file renamed to swip-74.md

One open point remains, marked (?): a second stream authenticating as the admin
while one is live.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…oth publisher streams

A second stream authenticating as the admin while one is live is admitted as a
publisher stream too; the per-cohort cursor arbitrates. No supersede rule, no
refusal. No (?) marks remain.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@zelig

zelig commented Sep 22, 2026

Copy link
Copy Markdown
Member

BPS-lite: counter-proposal

@acud — thanks for the draft; the line comments above are the piecemeal version of this,
so here is the whole position in one place. The rewritten draft is on this branch as
SWIPs/swip-74.md (two commits on top of yours; the number 74 is free). Points I have not
settled are marked open — they are questions to you, not decisions.

The position

The goal of PR #111 is right: a second-implementable target a tenth the size of SWIP-60,
for the one application everybody wants first — one author broadcasting to an audience.
Two things about how it gets there are wrong, and they are the whole counter-proposal:

  1. The point in the design space. Not ANCHOR with the publisher role bound to the
    opening stream, but the live-stream configuration of SWIP-60's table: a feed topic with an explicit
    admin in the spec. One address for every message (ANCHOR) leaves messages
    distinguishable only by payload, needs a dedup window, has no order and no link to
    storage. A feed gives order, gap detection, replay-freedom and persistence for the
    price of one address in the spec and one owner check per message — and the live
    stream is the feed, which is what makes SWIP-65 and history possible later without
    changing a frame.
  2. Subset, not divergence. The draft specifies "its own document rather than a
    profile", claims no compatibility in either direction, and makes ANCHOR
    unconditional so that SWIP-60 code "would perform no address check at all". That is
    two protocols under one id. BPS-lite must be the base of the family: the fuller
    protocol extends its wire by reserved numbers and never changes it, so a lite peer at
    a full broker is simply a conformant peer, and a lite broker refuses at the handshake
    everything it does not implement.

Everything else follows. What BPS-lite is, in one paragraph:

SWIP-74, BPS-lite. Three frames on pubsub/1.0.0Join{CohortSpec, Auth?},
Ack{Status}, Message{soc} — and the two types they carry. The spec is
{topic, FEED_TOPIC, admin} and is the
cohort's identity: a Join creates the cohort if no live one has that spec, attaches
otherwise; anyone may do either. Auth in Join binds an identity to the stream
(not the connection); a stream whose identity is the admin is a publisher stream, every
other stream is read-only. Messages are feed updates carried with the bare index in the
id slot; the broker keeps one cursor per cohort and accepts iff index > cursor, the
chunk validates as a SOC under keccak256(topic ‖ index), and its owner is the admin;
it delivers the bytes unchanged to every subscriber stream and to no publisher stream.
No dedup window, no loopback, no
service messages, no history, no incentives, no Bee API, one hop. A cohort ends by
inactivity, and by nothing else. Broker bounds: subscribers per cohort, cohorts per
broker, cohorts per peer connection, an inactivity deadline, a per-stream queue.

The ladder: what each step adds

Capability steps, not dated releases: each is a feature combination the family wants
to support, each extends the previous one's wire without changing it, and each is its
own SWIP (or an existing one).

step adds wire where
base single publisher over a feed, one broker, one hop Join, Ack, Message SWIP-74 (this)
MVP 1 (1) service messages — the admin's control plane as SOCs on a feed it owns: end of stream, later roster; (2) the Bee API — WS bridge, GET /pubsub/{topic}, POST /pubsub/{topic}/service, client-side signing, swarm-soc-fields no new message: service SOCs travel as Message with a full 32-byte id (which is why the base drops rather than punishes such ids); open: whether Ack gains the latest service SOC in its reserved fields SWIP-60, amended
MVP 2 multiple publishers: the admin publishes a roster; a cohort is multi-publisher iff it ever does (no regime in the spec); stream upgrade — a subscriber stream whose Join carried an Auth is promoted to a publisher stream in place when the roster names its identity, and demoted per SWIP-60's two-phase revocation. Validation generalises: owner ∈ roster, one feed and one cursor per publisher; delivery to every stream except the publishing identity's own no new message: the identity was bound at Join, the roster is a service SOC; open: an Upgrade{Auth} frame for a stream that joined without Auth SWIP-60, amended
MVP 3 self-indexed feeds — SWIP-65's payload construction (whirl-only pot), gap recovery, equivocation check, publish-is-upload no new message: carriage is already the bare index; SWIP-65's "broker does not enforce monotonicity" gets the single-publisher exception the base's cursor is SWIP-65, one-line amendment
MVP 4 multihop — FCFS multicast tree, referral at capacity, dual parents new fields on Message, which the base ignores SWIP-61

Not on the ladder, orthogonal: bandwidth incentives (bps-bw-incentives, DISC: YES),
broker discovery (SWIP-59 MEX), history for non-self-indexed bindings (bps-history).

Why Auth is in Join even in the base, where only an identity that recovers to the
admin has any effect:

MVP 2's promotion. An identity bound to the stream at join time is what lets the broker
flip a stream's role in place when a roster arrives, without a rejoin and without
tying the identity to the peer connection (one node, several cohorts, several keys).

Why the base has no end-of-stream: an end that is signed by the admin is a service
message (MVP 1); an end inferred from the admin's stream makes every transport hiccup a
stream-ending event for the audience. So the base has inactivity only, and "over" versus
"paused" is the application's to carry until MVP 1.

What changes against PR #111, by section

PR #111 counter-proposal
ANCHOR, unconditional; no publisher field FEED_TOPIC; admin in the spec; owner check per message
Hello{Open | Subscribe}, Open on a live topic transfers the role one Join carrying the spec; create-or-attach; role from Auth
Open.loopback no loopback, ever: a publisher never receives its own messages
Subscribe before OpenUNKNOWN_TOPIC anyone creates; no unknown topic
Ack{status, spec} Ack{status}
Soc broken into five proto fields the chunk as opaque chunk data, validated by the ordinary SOC code
Publish / Broadcast{oneof} with reserved 2–15 one Message, both directions
validation: address == topic, BMT, dedup window validation: publisher stream, index > cursor, SOC under H(topic ‖ index), owner == admin
dedup: LRU of wrapped CACs, 1024 no window: the cursor
lifetime = the publisher's stream; teardown resets subscribers lifetime = inactivity deadline; a publisher stream going away is not an end
publisher-stream queue exemption moot without loopback
"no compatibility claimed in either direction" strict subset; SWIP-60 amended to extend this wire
test cases 1–13 replaced by SWIP-74's conformance section (items 1–11): only the reserved-field, subscriber-publishes, FULL-without-referral and queue tests survive in substance; new: cursor rule, owner rule, identity-on-stream, inactivity, interop with a full broker
implementation section dropped from the SWIP

Editorial: the file becomes SWIPs/swip-74.md; the duplicated "superset of this spec"
paragraph under Implementation goes with the section.

Extra comments and clarifications

  • SWIP-60 amendments implied. The base is only a subset if SWIP-60 is amended to
    extend it: Hello/Open/SubscribeJoin carrying the spec, with cohorts keyed
    by the whole spec rather than by topic (so pre-creating a topic under a wrong admin
    squats nothing); GENESIS dropped (with the spec in Join there is nothing left for
    it to prove) and no spec echo in Ack; the PublisherRegime enum reduced to the
    single value ALL (absent: the admin and whoever its roster ever names, so
    multi-publisher iff a roster is published); spectators dropped or reverted to
    closed
    — a proto3 bool
    whose unset value is false reads every lite spec as a closed cohort, and a full broker
    would refuse every lite subscriber; Soc as opaque chunk data. My to-do on PR SWIP-060: BPS singlehop — brokered broadcast pub/sub, base protocol #104;
    the next revision of assets/swip-60/bps.proto should be derived from SWIP-74's
    block, not the reverse.
  • FEED_TOPIC = 4. SWIP-74 keeps SWIP-60's enum number so the fuller protocol adds
    bindings without renumbering; it is the only value a lite broker accepts.
  • Opaque chunk data, not proto fields, for the SOC. Carry the chunk as bytes and let
    the SOC code validate. It removes a message type and any chance of the proto and the
    chunk format disagreeing. It is a wire change for SWIP-60's proto and for bee #5435's
    framing.
  • Several publisher streams. With the identity on the stream, nothing stops the
    admin from holding two publisher streams (two nodes, or a reconnect racing a stale
    stream). SWIP-74 allows it and lets the cursor arbitrate, rather than the "new stream
    supersedes the old" rule of the first draft: allowing it is simpler and is what MVP 2
    needs anyway. Deliveries go to subscriber streams only, so neither publisher stream
    ever sees the admin's messages.
  • A misconfigured admin is admitted read-only. An Auth that fails to recover to
    admin (wrong topic bytes, wrong key) gets OK and a subscriber stream, and its
    first publication is a violation. Ack is status only, so the client cannot learn
    this from the broker; a client library checks its own Auth recovers to the admin
    it is about to send before joining — it has everything it needs to.
  • Replay across cohorts. Auth is replayable (static preimage, as in SWIP-60), and
    a replayer also holds the admin's signed history. Within a cohort's life the cursor
    refuses it; after a reclaim, or at another broker, the base cannot tell replayed
    history from history. Subscribers keep their cursor per (topic, admin); first-time
    freshness is the payload's business (SWIP-65's timestamp key) until service messages
    exist.
  • A non-admin Auth in the base is accepted as a subscriber stream carrying an
    identity, not REJECTED — so that the same Join works unchanged when MVP 2
    promotes it, and so that a lite broker does not refuse for identity, which SWIP-60
    reserves for spectators: false.
  • Retransmits. index ≤ cursor is counted, not punished: it is what an admin
    reconnecting after a reset legitimately does when it does not know what the broker
    last accepted. Open: whether Ack should carry the cursor to the admin on rejoin —
    one field, saves a round of retransmits; left out of the base for now.
  • Number. 74 is free: no PR, branch or file in ethersphere/SWIPs uses it (the only
    "74" is PR add SWIP-39: balanced neighbourhood registry aka smart neighbourhood management #74, SWIP-39), and 73 is PR SWIP-073: Custody separation #109.

zelig added a commit that referenced this pull request Sep 22, 2026
…SIS, regime = ALL, closed, opaque chunk

SWIP-60 now extends the base wire of SWIP-74 (BPS-lite, PR #111) and changes
nothing in it. bps.proto is revision 8, derived from SWIP-74's block.

- one handshake frame, Join{CohortSpec, Auth?}; Hello/Open/Subscribe gone; cohorts
  keyed by the whole spec (create-or-attach, no UNKNOWN_TOPIC, squatting a topic
  under a wrong admin obtains nothing)
- Ack is a status; the latest service SOC is delivered as the first Message on
  every newly attached stream not bound to the admin (marked open)
- GENESIS gone: the spec is in every Join; the service feed starts at index 0 with
  the first ROSTER or END_OF_STREAM, and each service message carries its index
- publisher regime reduced to the single value ALL; unset = the admin and whoever
  its roster ever names; ADMIN_ONLY and GRANTED gone; live-stream and
  spectator-jam are one spec
- spectators (field 9) reverted to closed (field 8, unset = open audience)
- one Message{soc} frame both directions, chunk as opaque chunk data; Publish,
  Broadcast and the field-level Soc gone; deliveries to every stream not bound to
  the publishing identity
- Auth bound to the stream; a spectator carrying an identity is promoted in place
  when a roster names it; the admin's Join is admitted past the per-cohort bound
- broker validation restated: duplicates are retransmits, never invalid; service
  SOCs recognised by id before the content path; a message from a non-publishing
  stream is a violation
- the feed cursor stays SWIP-74's stricter special case; a full broker dedups on
  chunk address (several publisher feeds, SWIP-61 reordering)
- ANCHOR under explicit authorship: no address check, topic is a rendezvous
- capacity, streams and limits per cohort, not per topic; SWIP-74's bounds
- API: spec parameters on every session, auth binds an identity, worked URLs
  updated, closed replaces spectators
- conformance items 1, 2, 5, 6, 7, 8 updated; title, motivation, security and
  backwards compatibility aligned; SWIP-61 to be re-based on the Message frame

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants