Skip to content

gossipsub: Topic Streams Extension - #729

Open
MarcoPolo wants to merge 2 commits into
masterfrom
marco/topic-message-streams
Open

gossipsub: Topic Streams Extension#729
MarcoPolo wants to merge 2 commits into
masterfrom
marco/topic-message-streams

Conversation

@MarcoPolo

Copy link
Copy Markdown
Contributor

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.

@MarcoPolo
MarcoPolo requested a review from sukunrt July 8, 2026 16:45
@github-project-automation github-project-automation Bot moved this to Triage in libp2p Specs Jul 8, 2026
@MarcoPolo
MarcoPolo requested a review from jxs July 8, 2026 16:55
Comment thread pubsub/gossipsub/topic-streams.md Outdated
Comment on lines +63 to +66
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.

@sukunrt sukunrt Jul 14, 2026

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.

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:
Easier implementation. Need to read a control scoped RPC.
Heartbeat can now be per topic and be staggered by having different topics at different times, and also more frequent for some topics.
Less HOL Blocking when sharing IHave, IWant, IDontWant

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?
1 topic control stream as defined above
1 topic data stream per message.
With this design we get nice cancellation properties. So we can cancel in progress sends easily. And if we add message preambles / ids before sending messages we can also reset on the receive side.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines +81 to +86
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
downscore peers that open more. Initiators SHOULD limit the number of
concurrent topic streams to 1 per topic. The initiator MUST close the old
stream before writing on a new stream for a given topic.

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.

it's a bit confusing, why would there be 3 streams?

Is this because of subscribe, unsubscribe, subscribe, unsubscribe kind of pattern?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

MarcoPolo pushed a commit to sierrasystems-ai/test-plans that referenced this pull request Jul 29, 2026
Add a Shadow scenario that publishes a 1MiB message then a 1KiB message
on different topics, and a log-timestamp check that the small message is
not head-of-line blocked. Wire the go binary to the topic-streams pubsub
fork (libp2p/specs#729) with WithTopicStreams enabled.
Comment on lines +42 to +44
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the message violates the condition, no? not the peer

Suggested change
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
If a peer receives a message 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 +45 to +47
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`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
A peer opens a bidirectional stream for each topic that it wishes to send
A peer MUST open a bidirectional stream for each topic that it wishes to send

Comment on lines +56 to +57
The responder of the bidirectional stream MUST NOT write on the stream after
protocol negotiation completes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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


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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comment on lines +84 to +85
downscore peers that open more. Initiators SHOULD limit the number of
concurrent topic streams to 1 per topic. The initiator MUST close the old

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Initiators SHOULD limit the number of concurrent topic streams to 1 per topic

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?

Comment on lines +88 to +90
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

it SHOULD downscore the peer

Is this true for other spec violations here as well? Maybe we should include this sentence in other places as well

Comment on lines +90 to +92
downscore the peer. The receiver MUST NOT downscore a peer for opening a topic
stream for a topic the receiver recently unsubscribed from, as the peer may not
have received the unsubscribe message before opening the topic stream.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit

Suggested change
downscore the peer. The receiver MUST NOT downscore a peer for opening a topic
stream for a topic the receiver recently unsubscribed from, as the peer may not
have received the unsubscribe message before opening the topic stream.
downscore the peer.
**Note:** The receiver MUST NOT downscore a peer for opening a topic
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 +105 to +106
When a peer wishes to publish a message, it MUST publish a `TopicScopedMessage`
and it MUST NOT publish a message on the control stream.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comment on lines +119 to +121
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit

Suggested change
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.
If the partial message extension has been negotiated with this extension, peers
MUST send each other Partial Messages on the topic stream.
Peers MUST NOT send Partial Messages on the control stream.

Comment on lines +78 to +79
`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.

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 data length of the application message MUST be non zero

Is this restriction mandatory? because pubsub does allow empty data.
https://github.com/libp2p/specs/blob/master/pubsub/README.md#the-message

Comment on lines +36 to +38
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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):

  1. A and B write topicStreams=true on their gossip streams
  2. A receives B's extension advertisement so A knows that both sides support the extensions
  3. A opens /gsts/v0beta
  4. But, because the streams are muxed independently, B can receive the topic stream before it read A extension advertisement,
  5. Then in that moment B knows it support topic streams, however it still does not know that A supports it.

Should A open the stream ONLY after receiving the extension advertisement from B? or should B assume A opening a stream for /gsts/v0beta is proof enough that A supports topic streams?

Comment on lines +96 to +98
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
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.
A topic stream is created when a node publishes a topic message to a peer. It is
closed when the peer unsubscribes or the sender no longer intends to send
messages on that topic. Either side may close the stream.

Comment on lines +74 to +75
// Included for computing signatures, not used on the wire
optional string unset_topic_name = 4;

Copy link
Copy Markdown

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 ?

Suggested change
// Included for computing signatures, not used on the wire
optional string unset_topic_name = 4;
// Included for computing signatures, not used on the wire
reserved 4;

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
downscore peers that open more. Initiators SHOULD limit the number of

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

4 participants