Skip to content
Open
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
141 changes: 141 additions & 0 deletions SWIPs/assets/swip-60/bps.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// 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 {

Copy link
Copy Markdown

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

  • confusing, not very approachable, does not make sense, very dumb, no topic semantics, hmmm, thats a lot of negative things to asspciated to something that could have different semantics according to its type which makes for a... complex API surfaces? hhwhhat?

@acud acud Aug 5, 2026

Copy link
Copy Markdown

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I quoted your words which indeed were unnecessarily agressive.
As for your original intention, what was it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not sure the semantics of topic or pubsub changes here, I thinkk the various bindings merely link the updates on a topic differently to each other as well as allow for multiple sources

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

why do we need this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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.
// NOTE: the proximity constraint for implicit bindings is a protocol
// constant, PO_MIN = 16 — not a cohort parameter (a proto3 unset uint32 is
// indistinguishable from 0, which would silently disable the constraint;
// and no use case varies it).
message CohortSpec {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 CohortSpec when connecting? i.e. why is it necessary to mention the admin, history, publishers, etc? seems like irrelevant information. i can't see why a subscriber should provide anything more than a topic and an identity, and that's it.

so it begs the question, why shouldn't a state channel opening become its own specific message? and then equally Connect turns into a Subscribe

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
reserved 7; // was po_min — now protocol constant PO_MIN
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.
Loading