-
Notifications
You must be signed in to change notification settings - Fork 32
add SWIP-60: BPS singlehop — brokered broadcast pub/sub, base protocol #104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
d0fd952
25f6f08
77f6088
7481286
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| // Broadcast Pub/Sub (BPS) — protocol messages and types. | ||
| // Spec: SWIP-60 (../../swip-60.md). | ||
| // | ||
| // Revision 2 (2026-08-05), after review on PR #104: Connect split into | ||
| // Open/Subscribe (subscribers carry no cohort metadata), broker capacity | ||
| // removed from CohortSpec (it is broker-side policy, not a cohort parameter), | ||
| // Ping dropped (liveness/RTT are transport concerns), and every frame carries | ||
| // the full SOC (no handshake/data split). Field numbers renumbered — the | ||
| // draft has no deployed compatibility surface. | ||
| // | ||
| // Enum zero values (*_UNSPECIFIED): proto3 requires a zero value; it is | ||
| // deliberately NOT a legitimate wire value. It exists so that an unset field | ||
| // is detectable and no implementation can silently rely on a default. | ||
| // Receivers MUST reject messages carrying it. | ||
| // | ||
| // The singlehop (depth = 1) subset is concrete; multihop control-plane | ||
| // messages are reserved. Implementation groundwork: bee PR #5435 | ||
| // (hand-rolled byte framing with the same semantics). | ||
|
|
||
| syntax = "proto3"; | ||
| package bps; | ||
|
|
||
| option go_package = "github.com/ethersphere/bee/v2/pkg/bps/pb"; | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Cohort genesis — the primitive decisions whose combinations are the "modes" | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| // What the topic binds to (see SWIP-60: binding semantics). | ||
| enum TopicBinding { | ||
| TOPIC_BINDING_UNSPECIFIED = 0; // invalid on the wire (see header note) | ||
| ANCHOR = 1; // topic = full SOC/GSOC address; dedup on the wrapped CAC | ||
| SOC_ID = 2; // topic = SOC id; any owner with PO(addr, anchor) >= po_min | ||
| OWNER = 3; // topic = SOC owner; any id with PO(addr, anchor) >= po_min (MIC) | ||
| FEED_TOPIC = 4; // id = keccak256(topic ‖ index); graffiti MIC / feed streams | ||
| } | ||
|
|
||
| // Who may author. | ||
| enum PublisherRegime { | ||
| PUBLISHER_REGIME_UNSPECIFIED = 0; // invalid on the wire (see header note) | ||
| EXPLICIT_SINGLE = 1; // opener is the sole publisher (live streaming) | ||
| EXPLICIT_LIST = 2; // set fixed at genesis: admin + publisher_list | ||
| // (dynamic grants/revocations: later revision) | ||
| IMPLICIT = 3; // authorship implied by the topic binding (PO constraint) | ||
| ALL = 4; // every peer publishes (gossipsub-equivalent cohort) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need this?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. either ALL or EXPLICIT list needed. HOnestly I do not find it very natural that you can edit a file ith 3 other random people :) you want to restrict, explicitly list those that do . |
||
| } | ||
|
|
||
| // Fixed by the cohort's opener; immutable for the cohort's lifetime. | ||
| // NOTE: broker capacity is NOT a cohort parameter — a cohort cannot dictate a | ||
| // remote node's connection count. Each broker enforces its own per-topic | ||
| // stream limit and answers FULL when it is exhausted. | ||
| message CohortSpec { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the comment is misleading and partially incorrect. the seq diagram in the markdown file defines that actually both publisher and subscriber use the same type of message to connect to a broker. i'm not sure what are my feelings around this. it seems to be too elaborate for both sides to use symmetrically - why does a subscriber need to provide the whole so it begs the question, why shouldn't a state channel opening become its own specific message? and then equally |
||
| bytes topic = 1; // 32 bytes, meaning per binding | ||
| TopicBinding binding = 2; | ||
| PublisherRegime publishers = 3; | ||
| bool history = 4; // deliver matching chunks from the local store | ||
| bytes admin = 5; // 20-byte eth address; set iff EXPLICIT_* | ||
| repeated bytes publisher_list = 6; // 20-byte eth addresses, excl. admin; | ||
| // set iff EXPLICIT_LIST | ||
| uint32 po_min = 7; // proximity order for implicit bindings (default 16) | ||
|
|
||
| bool closed = 8; // no audience: subscribers restricted to the publishers | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // 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. | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| // Opener -> broker: the one peer that fixes the cohort. | ||
| message Open { | ||
| CohortSpec cohort = 1; | ||
| PublisherAuth auth = 2; // present iff the opener publishes (explicit regimes) | ||
| } | ||
|
|
||
| // 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). | ||
| message Subscribe { | ||
| bytes topic = 1; // 32 bytes | ||
| PublisherAuth auth = 2; // present iff publisher | ||
| } | ||
|
|
||
| message PublisherAuth { | ||
| bytes owner = 1; // 20-byte eth address of the SOC owner key | ||
| bytes id = 2; // 32-byte SOC id, when the binding fixes it | ||
| } | ||
|
|
||
| // Broker -> peer, answering Open or Subscribe. The echoed CohortSpec lets a | ||
| // subscriber verify every message end-to-end against the topic binding. | ||
| message Ack { | ||
| Status status = 1; | ||
| CohortSpec cohort = 2; // set iff status == OK | ||
| } | ||
|
|
||
| enum Status { | ||
| STATUS_UNSPECIFIED = 0; // invalid on the wire (see header note) | ||
| OK = 1; | ||
| FULL = 2; // broker at its per-topic capacity; | ||
| // a singlehop broker refuses — nothing else | ||
| UNKNOWN_TOPIC = 3; // Subscribe for a topic the broker does not serve | ||
| REJECTED = 4; // e.g. publisher not on the list, invalid auth, | ||
| // non-publisher Subscribe on a closed cohort | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Messages — SOC-only is a protocol feature | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| // A full single-owner chunk in transit. Every frame is self-contained: no | ||
| // per-stream handshake state, and no format change if the stream model | ||
| // evolves (e.g. topic-muxed streams later). | ||
| message Soc { | ||
| bytes id = 1; // 32 bytes | ||
| bytes owner = 2; // 20 bytes (recoverable from signature; explicit for cheap filtering) | ||
| bytes signature = 3; // 65 bytes | ||
| bytes span = 4; // 8 bytes LE | ||
| bytes payload = 5; // wrapped-CAC data, <= 4096 bytes | ||
| } | ||
|
|
||
| // Publisher -> broker. | ||
| message Publish { | ||
| Soc soc = 1; | ||
| } | ||
|
|
||
| // Broker -> subscriber. | ||
| message Broadcast { | ||
| oneof frame { | ||
| Soc soc = 1; | ||
| // 2–15 reserved: multihop control plane (Beacon, Reparent, Expect, | ||
| // DcutrSignal, SwapProposal) — named to fix intent, not final. | ||
| } | ||
| } | ||
|
|
||
| // Keepalive / RTT: none at the BPS level. Liveness is the transport's job | ||
| // (libp2p), and latency metrics for reorganisation policies (SWATCH) are | ||
| // sourced there as well. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,256 @@ | ||
| --- | ||
| SWIP: 60 | ||
| title: BPS singlehop — brokered broadcast pub/sub, base protocol | ||
| author: Viktor Trón (@zelig), Viktor Tóth (@nugaon) | ||
| discussions-to: https://discord.gg/Q6BvSkCv | ||
| status: Draft | ||
| type: Standards Track (Networking) | ||
| created: 2026-08-03 | ||
| --- | ||
|
|
||
| <!-- Base SWIP of the Broadcast Pub/Sub (BPS) family: the decomposition of the monolithic | ||
| PubSub SWIP (ethersphere/SWIPs PR #93) into work-package-sized SWIPs. Companion protobuf: | ||
| assets/swip-60/bps.proto. --> | ||
|
|
||
| - **Business line**: real-time topic streams for dApps without storing chunks or polling — | ||
| enough on its own for small closed collaboration cohorts (collaborative remix editing, a | ||
| strudel livecoding session, multiparty games) and basic single-publisher limited-audience | ||
| live streaming. | ||
| - **Dev line**: implement one libp2p protocol (`pubsub/1.0.0`, messages in | ||
| [bps.proto](assets/swip-60/bps.proto)) plus a WebSocket bridge on the Bee API; done when | ||
| a broker, publishers and subscribers interoperate per the conformance section. Groundwork | ||
| exists in bee [#5435](https://github.com/ethersphere/bee/pull/5435). | ||
| - Bandwidth-incentive integration is a separate SWIP (bps-bw-incentives). | ||
| - Broker discovery integration is from a separate SWIP (bps-broker-discovery, building on | ||
| [SWIP-59 MEX](https://github.com/ethersphere/SWIPs/pull/103)). | ||
|
|
||
| ## Simple Summary | ||
|
|
||
| A real-time messaging protocol: WebSocket clients publish and subscribe to topic streams | ||
| through Bee nodes. One full node per topic acts as **broker**, re-broadcasting each message | ||
| over direct, long-lived p2p streams to a capacity-bounded set of connected peers. Messages are | ||
| single-owner chunks, so every subscriber verifies authorship end-to-end; the broker can | ||
| withhold, never forge. | ||
|
|
||
| ## Motivation | ||
|
|
||
| Swarm's event primitives (GSOC, PSS) require full-node operation; light clients can only | ||
| poll storage. BPS singlehop is the smallest protocol that fixes this: one broker, direct | ||
| streams, authenticated messages, an explicit capacity bound. Everything larger — multihop | ||
| trees, adaptive reorganisation, incentives, discovery — is layered on top by later SWIPs | ||
| without changing the semantics defined here. | ||
|
|
||
| ## Specification | ||
|
|
||
| ### The contract | ||
|
|
||
| Per topic-cohort: | ||
|
|
||
| - messages come from **publishers, and publishers only**; | ||
| - they arrive at **all subscribers**. | ||
|
|
||
| ### Cohort genesis: the parameters | ||
|
|
||
| A cohort is fully described by a `CohortSpec` ([bps.proto](assets/swip-60/bps.proto)), | ||
| fixed the moment the first peer contacts a BPS-speaking full node with a topic. There is | ||
| no mode enum; **modes are combinations of these parameters**. | ||
|
|
||
| | parameter | values | meaning | | ||
| |---|---|---| | ||
| | `topic` | 32 bytes | interpreted per `binding` | | ||
| | `binding` | `ANCHOR` / `SOC_ID` / `OWNER` / `FEED_TOPIC` | what the topic binds to; fixes which SOCs qualify as messages and the dedup rule | | ||
| | `publishers` | `EXPLICIT_SINGLE` / `EXPLICIT_LIST` / `IMPLICIT` / `ALL` | who may author | | ||
| | `admin` + `publisher_list` | eth addresses | set iff explicit publishers; with `EXPLICIT_LIST` the full publisher set is **fixed at genesis** (dynamic grants/revocations are deferred to a later revision) | | ||
| | `history` | bool | deliver matching chunks already in the local store (mechanism in bps-history; a singlehop broker MAY refuse) | | ||
| | `po_min` | uint (default 16) | proximity constraint for implicit bindings: `PO(socAddr, anchor) ≥ po_min` | | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. po_min should be a constant not. a param |
||
| | `closed` | bool | no audience: subscribers are restricted to the publisher set (all and only publishers subscribe) | | ||
|
|
||
| Broker **capacity is deliberately not a cohort parameter**: a cohort cannot dictate a | ||
| remote node's connection count. Each broker enforces its own per-topic stream limit and | ||
| answers `FULL` when it is exhausted. | ||
|
|
||
| Binding semantics (dedup rule in parentheses): | ||
|
|
||
| - **`ANCHOR`** — topic = full SOC/GSOC address; all messages share one address (dedup on | ||
| the wrapped CAC). | ||
| - **`SOC_ID`** — topic = SOC id; any owner with `PO(socAddr(id, owner), anchor) ≥ po_min` | ||
| qualifies (dedup on chunk address). | ||
| - **`OWNER`** — topic = SOC owner; any id under the same PO constraint — MIC semantics | ||
| (dedup on chunk address). | ||
| - **`FEED_TOPIC`** — id = `keccak256(topic ‖ index)`; feed-update streams, graffiti MIC | ||
| (dedup on chunk address). | ||
|
|
||
| ### Roles and capacity | ||
|
|
||
| - **Broker**: the first full node contacted; root of the (here, depth = 1) multicast tree. | ||
| Enforces its own per-topic capacity. **At capacity it MUST answer `Open`/`Subscribe` | ||
| with a refusal** (`FULL`); referral to another attachment point is reserved for | ||
| bps-multihop — a singlehop-only broker simply refuses. | ||
| - **Opener**: the one peer that fixes the `CohortSpec` (`Open`); with explicit publisher | ||
| regimes the opener publishes. | ||
| - **Publisher**: sends and receives. MUST be directly connected to the broker; direct | ||
| connection is necessary, not sufficient — with explicit publishers, the genesis list | ||
| decides. | ||
| - **Subscriber**: receives only; joins by naming the topic (`Subscribe`) and carries no | ||
| cohort metadata — the broker echoes the `CohortSpec` back so every message can be | ||
| verified end-to-end. Does not exist in `closed` cohorts. | ||
|
|
||
| ### Information flow | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| autonumber | ||
| participant PD as publisher dApp | ||
| participant PN as publisher's bee node<br/>(WS bridge) | ||
| participant B as broker<br/>(root, full node) | ||
| participant SN as subscriber's bee node<br/>(WS bridge + mux) | ||
| participant SD as subscriber dApp(s) | ||
|
|
||
| PN->>B: Open(CohortSpec, auth) | ||
| Note over PN,B: opener fixes the cohort; publisher ⇒<br/>direct connection to broker | ||
| B-->>PN: Ack(OK) | ||
| SN->>B: Subscribe(topic) | ||
| B-->>SN: Ack(OK, CohortSpec) | ||
| Note over B,SN: echoed spec ⇒ subscriber verifies<br/>every message end-to-end | ||
|
|
||
| PD->>PN: WS: payload | ||
| PN->>B: Publish(SOC) | ||
| B->>B: validate: SOC sig ⊨ topic binding<br/>(+ dedup per binding) | ||
|
|
||
| par fan-out to every subscriber stream | ||
| B->>SN: Broadcast(SOC) — every frame self-contained | ||
| SN->>SN: mux: one p2p stream → N WS sessions | ||
| SN->>SD: WS: payload | ||
| and publisher's own subscription (if subscriber too) | ||
| B->>PN: Broadcast(SOC) | ||
| PN->>PD: WS: payload | ||
| end | ||
| ``` | ||
|
|
||
| The broadcast is **end-to-end authenticated**: every subscriber re-verifies the SOC | ||
| signature against the topic binding regardless of path. | ||
|
|
||
| ### Wire protocol | ||
|
|
||
| Messages are defined in [bps.proto](assets/swip-60/bps.proto). Framing notes: | ||
|
|
||
| - Transport: libp2p stream `pubsub/1.0.0`, one stream per (peer, topic), | ||
| protobuf-over-libp2p as bee protocols elsewhere. The first message on a fresh stream is | ||
| `Open` (fixes a new cohort) or `Subscribe` (joins one — topic only, no cohort | ||
| metadata); the broker answers with `Ack`, echoing the `CohortSpec` to subscribers. | ||
| - **Stream model rationale**: per-topic streams give per-cohort flow control, teardown | ||
| and role typing, and match bee's protocol idiom. Because every frame carries the full | ||
| SOC (self-contained, no per-stream handshake state), a later move to topic-muxed | ||
| streams requires no format change. | ||
| - Every `Broadcast` frame carries the **full SOC** (id, owner, signature, span, payload); | ||
| there is no handshake/data frame split. | ||
| - No BPS-level keepalive or RTT probing: liveness is the transport's job, and latency | ||
| metrics for reorganisation policies are sourced there too. | ||
| - Broker validation on `Publish`: SOC signature verifies against the topic binding, PO | ||
| constraint holds where applicable, sender is a legitimate publisher, message is not a | ||
| duplicate per the binding's dedup rule. Invalid ⇒ drop; repeated invalid ⇒ disconnect | ||
| (blocklisting policy). | ||
|
|
||
| ### API (WebSocket bridge) | ||
|
|
||
| WS clients see raw mode payloads only; all p2p framing is transparent. One p2p stream is | ||
| muxed to N local WS sessions per topic. Endpoint shape per bee | ||
| [#5435](https://github.com/ethersphere/bee/pull/5435). | ||
|
|
||
| ### Configurations (worked examples) | ||
|
|
||
| Modes are rows over the parameters; two normative examples: | ||
|
|
||
| **The 4-seat jam cohort** — collaborative remix editing, a strudel livecoding session, a | ||
| multiparty game. | ||
|
|
||
| ``` | ||
| binding: ANCHOR (topic = mnemonic anchor) publishers: EXPLICIT_LIST (admin + 3) | ||
| closed: true (all and only publishers subscribe) history: false | ||
| ``` | ||
|
|
||
| Every seat sends and receives; there is no audience; the genesis list **is** the seat | ||
| bound — a fifth peer's `Subscribe` gets `REJECTED`. | ||
|
|
||
| **Basic live streaming** — single publisher, open audience: | ||
|
|
||
| ``` | ||
| binding: FEED_TOPIC (sequential index) publishers: EXPLICIT_SINGLE | ||
| closed: false history: false | ||
| ``` | ||
|
|
||
| ### The modes — enumerated as combinations of dimension choices | ||
|
|
||
| Known use cases attach here; each mode is nothing more than a row — a combination of | ||
| publisher/subscriber info, topic match type, and history. (`+/−` = both configurations | ||
| meaningful.) | ||
|
|
||
| | # of pubs | pubs implicit? | subscribers | topic / anchor match | history | use case | | ||
| |---|---|---|---|---|---| | ||
| | 1 | — | all | feed topic, index sequential | — | live video streaming | | ||
| | any | — | all | feed topic, index sequential | — | live videoconference | | ||
| | — | + | all | feed topic | +/— | tags, adverts; private co-authoring | | ||
| | all | — | all | topic a mere mnemonic of the cohort | +/— | gossip cohort for multi-party / group chat | | ||
| | any | + | all | anchor (ephemeral GSOC) | +/— | anythread comments / troll-box | | ||
| | any | + | all | ID = `keccak256(topic ‖ index)` | +/— | following one or more feeds | | ||
| | — | + | all | feed special, mined index | +/— | following graffiti soc | | ||
|
|
||
| The audience is bounded by the broker's capacity; scaling past it is bps-multihop's | ||
| business. | ||
|
|
||
| Rows requiring implicit publishers or history are specified in bps-implicit-publisher and | ||
| bps-history respectively. | ||
|
|
||
| ## Rationale: why not gossipsub | ||
|
|
||
| libp2p ships gossipsub, a battle-tested mesh multicast. BPS builds its own protocol | ||
| because gossipsub's core mechanisms — flooding to a random mesh, IHAVE/IWANT | ||
| pull-recovery — are exactly what an incentivised network rejects: **no node wants to pay | ||
| for a message it did not ask for.** That one economic fact dissolves gossipsub's | ||
| machinery: metered edges mean no redundant paths and no transport-level duplicates; a | ||
| cohort's `CohortSpec` scopes every session; authentication is structural (SOC-signed | ||
| against the topic binding), so brokers and relays forward without being trusted — an | ||
| intermediate can withhold, never forge; and withholding is a liveness fault recoverable | ||
| by re-pointing or relocating the topic. Multihop forwarding (bps-multihop) adds capacity | ||
| without reintroducing flooding: every edge still pays upstream, every node still receives | ||
| only its topic's stream. | ||
|
|
||
| ## Out of scope (deliberately) | ||
|
|
||
| Multihop relaying and referral (bps-multihop), reorganisation policies (SWATCH, SPORE — | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i would also add to this: remove multi-publisher setup from this iteration. it can be added later and just adds more review surface to deal with at this moment.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. funnily i think a single one only is more complex to implement since you need to authenticate. |
||
| policy SWIPs over this protocol's events and actions, no new frames), bandwidth incentives | ||
| (bps-bw-incentives), broker discovery (SWIP-59 MEX; early deployments hardcode brokers), | ||
| history delivery mechanism (bps-history), implicit-publisher event sourcing | ||
| (bps-implicit-publisher), and **dynamic publisher-list changes** — grants/revocations | ||
| after genesis are deferred to a later revision; the `EXPLICIT_LIST` set is fixed at | ||
| `Open`. | ||
|
|
||
| ## Conformance (definition of done) | ||
|
|
||
| An implementation is conformant when: | ||
|
|
||
| 1. a broker enforces its per-topic capacity, publisher legitimacy, per-binding validation | ||
| and dedup; | ||
| 2. a subscriber re-verifies every message end-to-end (against the `Ack`-echoed | ||
| `CohortSpec`) and detects (only) liveness faults; | ||
| 3. the two worked configurations above interoperate across independent implementations | ||
| against the frames in [bps.proto](assets/swip-60/bps.proto); | ||
| 4. a `FULL` refusal is issued at capacity — and nothing else is (no referral). | ||
|
|
||
| ## Backwards compatibility | ||
|
|
||
| New protocol; no existing behaviour changes. Reserved `Broadcast` frame fields hold the | ||
| multihop control plane, so bps-multihop extends without a version bump; self-contained | ||
| frames mean a change of stream model needs no format change either. | ||
|
|
||
| ## References | ||
|
|
||
| Wire: [bps.proto](assets/swip-60/bps.proto) · origin: | ||
| [PR #93](https://github.com/ethersphere/SWIPs/pull/93) "Add: pubsub" · broker discovery: | ||
| [SWIP-59 MEX, PR #103](https://github.com/ethersphere/SWIPs/pull/103) · implementation: | ||
| bee [#5435](https://github.com/ethersphere/bee/pull/5435), bee-js | ||
| [#1151](https://github.com/ethersphere/bee-js/pull/1151) | ||
|
|
||
| ## Copyright | ||
|
|
||
| Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: i find this whole thing really confusing and not very approachable and i wonder if this even makes sense to do in a first iteration. "pubsub" is very dumb in this sense - it usually does not give you different topic semantics. here, a topic could have different semantics and input validation according to its "type" which makes for a much more complex API surfaces for users later on...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i meant the concept of pubsub usually does not offer different semantics over the concept of a topic. i would appreciate you not hijacking my words and initial intention as this is really counter productive and aggressive. thanks