-
Notifications
You must be signed in to change notification settings - Fork 317
gossipsub: Topic Streams Extension #729
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
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,128 @@ | ||||||||||||||||||
| # Topic Streams Extension | ||||||||||||||||||
|
|
||||||||||||||||||
| | Lifecycle Stage | Maturity | Status | Latest Revision | | ||||||||||||||||||
| | --------------- | ------------- | ------ | --------------- | | ||||||||||||||||||
| | 1A | Working Draft | Active | r0, 2026-06-29 | | ||||||||||||||||||
|
|
||||||||||||||||||
| Authors: [@marcopolo] | ||||||||||||||||||
|
|
||||||||||||||||||
| Interest Group: [@sukunrt], `TODO: expand` | ||||||||||||||||||
|
|
||||||||||||||||||
| [@marcopolo]: https://github.com/marcopolo | ||||||||||||||||||
| [@sukunrt]: https://github.com/sukunrt | ||||||||||||||||||
|
|
||||||||||||||||||
| See the [lifecycle document][lifecycle-spec] for context about the maturity level | ||||||||||||||||||
| and spec status. | ||||||||||||||||||
|
|
||||||||||||||||||
| [lifecycle-spec]: https://github.com/libp2p/specs/blob/master/00-framework-01-spec-lifecycle.md | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Overview | ||||||||||||||||||
|
|
||||||||||||||||||
| Gossipsub v1.3 uses a single stream per direction for all RPCs. This introduces | ||||||||||||||||||
| some problems: unnecessary head of line blocking between messages (especially | ||||||||||||||||||
| problematic when a large message in one topic delays small messages in another | ||||||||||||||||||
| topic) and topic name overhead on each message. | ||||||||||||||||||
|
|
||||||||||||||||||
| The Topic Streams Extension addresses these problems. It moves topic scoped | ||||||||||||||||||
| application messages to separate long-lived streams. | ||||||||||||||||||
|
|
||||||||||||||||||
| ## Negotiation | ||||||||||||||||||
|
|
||||||||||||||||||
| Peers MUST use this extension with each other when both sides advertise support | ||||||||||||||||||
| for this extension in the `ControlExtensions` message. | ||||||||||||||||||
|
|
||||||||||||||||||
| The `ControlExtensions` message is the first message a peer sends and comes at | ||||||||||||||||||
| the same time or earlier than a peer's subscriptions. Since a publisher should | ||||||||||||||||||
| not publish messages to a peer before learning of its subscriptions, there is | ||||||||||||||||||
| no window when a publisher wishes to publish a message, but does not know if | ||||||||||||||||||
| the peer supports Topic Streams. | ||||||||||||||||||
|
Comment on lines
+36
to
+38
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. What to do in case of races?: Assuming you have to peers (A and B) the following can happen (specially in quic):
Should A open the stream ONLY after receiving the extension advertisement from B? or should |
||||||||||||||||||
|
|
||||||||||||||||||
| ## Aborting the Connection on Protocol Violation | ||||||||||||||||||
|
|
||||||||||||||||||
| If a receiver receives a message from a peer that violates a MUST condition, | ||||||||||||||||||
| the receiver MUST reset the connection to the peer and send error code | ||||||||||||||||||
| `0xd52505` when the transport allows it. This code signifies a Topic Streams | ||||||||||||||||||
|
Comment on lines
+42
to
+44
Contributor
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 message violates the condition, no? not the peer
Suggested change
|
||||||||||||||||||
| Protocol Violation error, and is derived from the first 3 bytes of the sha256 | ||||||||||||||||||
| hashsum of the string `gossipsub-topic-streams`. (i.e. `echo -n | ||||||||||||||||||
| "gossipsub-topic-streams" | sha256sum | head -c 6`) | ||||||||||||||||||
|
Comment on lines
+45
to
+47
Contributor
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. Is this usual for specs to do? Otherwise, I see no point to explain where it comes from (maybe for curiosity in an appendix? |
||||||||||||||||||
|
|
||||||||||||||||||
| ## Topic Streams | ||||||||||||||||||
|
|
||||||||||||||||||
| A peer opens a bidirectional stream for each topic that it wishes to send | ||||||||||||||||||
|
Contributor
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.
Suggested change
|
||||||||||||||||||
| application messages for. Despite being bidirectional streams, they are treated | ||||||||||||||||||
| as unidirectional streams. If both sides wish to publish messages for a given | ||||||||||||||||||
| topic, both sides MUST open a bidirectional stream. | ||||||||||||||||||
|
|
||||||||||||||||||
| The responder of the bidirectional stream MUST NOT write on the stream after | ||||||||||||||||||
| protocol negotiation completes. | ||||||||||||||||||
|
Comment on lines
+56
to
+57
Contributor
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. So it is only bidirectional to accomodate the initial handshake? If so this should be stated explicitly |
||||||||||||||||||
|
|
||||||||||||||||||
| The protocol id for a topic stream is `/gsts/v0beta`. | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Control Stream | ||||||||||||||||||
|
|
||||||||||||||||||
| When this extension is negotiated, the original gossipsub stream becomes the | ||||||||||||||||||
| control stream. Application messages (such as the `Message` or | ||||||||||||||||||
| `PartialMessagesExtension` messages) MUST NOT be published on the control | ||||||||||||||||||
| stream. | ||||||||||||||||||
|
Comment on lines
+63
to
+66
Member
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. What do you think about a design where we have per topic control streams too? So the control stream would share the topic relevant: IHave, IWant, IDontWant, and maybe Graft and Prune This allows: And mostly the fact that there really isn't any reason that control messages for a topic should interleave with control messages of another topic. Similarly What do you think about?
Contributor
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. Discussed synchronously. Resolved that theoretically 1 topic control stream would be better, the actual utility doesn't justify its implementation complexity. Decided to leave as is. |
||||||||||||||||||
|
|
||||||||||||||||||
| ### Topic Stream Header | ||||||||||||||||||
|
|
||||||||||||||||||
| Upon opening the topic stream, the initiator MUST send a single length-prefixed | ||||||||||||||||||
| `TopicRPCHeader` protobuf message. The `TopicRPCHeader` MUST contain a topic. The receiver uses this to identify the topic associated with the stream. | ||||||||||||||||||
| Afterwards, only length-prefixed `TopicRPC` protobuf messages are allowed. | ||||||||||||||||||
|
|
||||||||||||||||||
| If the receiver does not receive a topic header within a reasonable timeout, it | ||||||||||||||||||
| SHOULD reset the stream. One second is a reasonable timeout for most | ||||||||||||||||||
| applications. | ||||||||||||||||||
|
|
||||||||||||||||||
| `TopicRPC` messages MUST NOT be empty. They MUST contain either a partial or | ||||||||||||||||||
| publish message. The data length of the application message MUST be non zero. | ||||||||||||||||||
|
Comment on lines
+78
to
+79
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.
Is this restriction mandatory? because pubsub does allow empty |
||||||||||||||||||
|
|
||||||||||||||||||
| If there are multiple streams for a single topic, the receiver SHOULD process | ||||||||||||||||||
| them in the order the streams were opened by the initiator. The receiver | ||||||||||||||||||
| SHOULD limit the number of concurrent topic streams for the same topic to 3 and | ||||||||||||||||||
|
Contributor
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 wonder if it'd be better to use variables here for this, as well as the stream reset timeout or other constants, and then add a table below with recommended default values for these. |
||||||||||||||||||
| downscore peers that open more. Initiators SHOULD limit the number of | ||||||||||||||||||
|
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. What happens with streams that are opened after the limit is reached? are they reset? |
||||||||||||||||||
| concurrent topic streams to 1 per topic. The initiator MUST close the old | ||||||||||||||||||
|
Comment on lines
+84
to
+85
Contributor
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.
Should we include an explanation about what to do when there are multiple concurrent topic streams for a single topic? Because I can't see how that would work exactly. If it makes no sense whatsoever, then maybe we can change that SHOULD to a MUST? |
||||||||||||||||||
| stream before writing on a new stream for a given topic. | ||||||||||||||||||
|
Comment on lines
+81
to
+86
Member
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. it's a bit confusing, why would there be 3 streams? Is this because of subscribe, unsubscribe, subscribe, unsubscribe kind of pattern?
Contributor
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. right, or if the implementation opens a new stream per message. Because there is a potential for packets to be reordered or delayed, the sender should avoid concurrent streams that could appear to the receiver as multiple concurrent streams open at the same time. |
||||||||||||||||||
|
|
||||||||||||||||||
| If the receiver receives a topic stream for a topic it is not subscribed to and | ||||||||||||||||||
| has not recently published partial messages to (via fanout), it SHOULD | ||||||||||||||||||
| downscore the peer. The receiver MUST NOT downscore a peer for opening a topic | ||||||||||||||||||
|
Comment on lines
+88
to
+90
Contributor
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.
Is this true for other spec violations here as well? Maybe we should include this sentence in other places as well |
||||||||||||||||||
| stream for a topic the receiver recently unsubscribed from, as the peer may not | ||||||||||||||||||
| have received the unsubscribe message before opening the topic stream. | ||||||||||||||||||
|
Comment on lines
+90
to
+92
Contributor
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. nit
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| ### Lifecycle | ||||||||||||||||||
|
|
||||||||||||||||||
| A topic stream is created when a node publishes a topic message to a peer. It is | ||||||||||||||||||
| closed when either the peer unsubscribes from the topic, or the publisher will | ||||||||||||||||||
| no longer publish on the topic. Either side may close the stream. | ||||||||||||||||||
|
Comment on lines
+96
to
+98
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.
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| Implementations MAY choose to keep topic streams open only for mesh and fanout | ||||||||||||||||||
| peers and use a short-lived stream when responding to `IWANTs`. | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Topic Scoped Messages | ||||||||||||||||||
|
|
||||||||||||||||||
| When a peer wishes to publish a message, it MUST publish a `TopicScopedMessage` | ||||||||||||||||||
| and it MUST NOT publish a message on the control stream. | ||||||||||||||||||
|
Comment on lines
+105
to
+106
Contributor
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. This got me thinking... how does a peer know that a message is not a control message? Apologies if I'm missing important gossipsub-related information about this. |
||||||||||||||||||
|
|
||||||||||||||||||
| Implementations MUST NOT set the topic name when sending the message over the | ||||||||||||||||||
| wire. | ||||||||||||||||||
|
|
||||||||||||||||||
| When verifying a message's signature or deriving a message's ID, | ||||||||||||||||||
| implementations MUST reconstruct the full `Message` by setting `topic` to the | ||||||||||||||||||
| value from the stream's `TopicRPCHeader`, and verify the signature against | ||||||||||||||||||
| that. Implementations SHOULD also populate the `topic` before delivering the | ||||||||||||||||||
| message to the application. | ||||||||||||||||||
|
|
||||||||||||||||||
| ### Partial Messages | ||||||||||||||||||
|
|
||||||||||||||||||
| If the partial message extension has been negotiated with this extension, peers | ||||||||||||||||||
| MUST send each other Partial Messages on the topic stream, not the control | ||||||||||||||||||
| stream. | ||||||||||||||||||
|
Comment on lines
+119
to
+121
Contributor
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. nit
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| `PartialMessagesExtension` protobuf messages MUST omit the `topicID` field when | ||||||||||||||||||
| sent over the wire. Implementations MUST set the `topicID` field after | ||||||||||||||||||
| reading the message from the wire. | ||||||||||||||||||
|
|
||||||||||||||||||
| NB: Protobuf bytes and strings are interchangeable; implementations are free to | ||||||||||||||||||
| choose the representation that works better for them. | ||||||||||||||||||
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.
Should this use
https://protobuf.dev/programming-guides/proto3/#reserved ?