Skip to content
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

Add an example that documents how to build an "escape hatch" NetworkBehaviour that just hands out streams #4457

Closed
thomaseizinger opened this issue Sep 6, 2023 · 7 comments · Fixed by #5027

Comments

@thomaseizinger
Copy link
Contributor

Simply getting access to a stream without having to implement NetworkBehaviour has been discussed a lot. Mostly in #2657 and other linked issues.

This issue documents an example that we would like to add to the repository which users can copy and modify to their needs. Perhaps we can eventually grow something useful out of that :)

The example should:

  • Have a binary and a library component
  • Binary listens on a random port and prints listen address
  • Binary accepts an address to dial and connects to it
  • Binary opens a stream, send 32 random bytes, waits for the echo and closes again

The above is essentially the ping protocol but implemented "outside" of rust-libp2p, i.e. not as a module. This should serve as a good-enough starting point for users that want to implement something custom.

The library component of the example should contain a NetworkBehaviour with roughly the following API:

/// A behaviour that manages requests to open new streams which are directly handed to the user.
pub struct Behaviour { }

/// A control acts as a "remote" that allows users to request streams without interacting with the `Swarm` directly.
#[derive(Clone)]
pub struct Control { }

impl Behaviour {
	pub fn new<F: Future<Output = io::Result<()>>>(protocol: StreamProtocol, handler: impl Fn(Stream) -> F) -> (Self, Control);
}

impl Control {
	pub async fn open_stream(&self, peer: PeerId) -> Result<Stream, Error>;
}

pub enum Error {
	NotConnected(PeerId),
	UnsupportedProtocol,
	Io(io::Error)
}

pub enum Event {
	InboundStreamFailed(io::Error),
}

A couple of design notes:

  • Only a single protocol is supported, if users need fallback to others, they can modify it.
  • Not sharing of state between handler invocations is supported. We use an Fn because it is simple. If state needs to be shared across inbound streams, it is better to just implement a ConnectionHandler as it will come out much cleaner.
  • The Control object allows creation of streams without interacting with Swarm. The assumption is that most users that want this kind of functionality don't want to interact with the Swarm through an event loop.
  • This behaviour does not support establishing connections. If users want to create or manage connections, they can easily extend the Control object with more messages.
@thomaseizinger thomaseizinger changed the title example: Add an example that documents how to build an "escape hatch" NetworkBehaviour that just hands out streams Add an example that documents how to build an "escape hatch" NetworkBehaviour that just hands out streams Sep 6, 2023
@p0mvn
Copy link
Contributor

p0mvn commented Sep 6, 2023

I would love to give this a shot. Can I get assigned to this if possible?

@thomaseizinger
Copy link
Contributor Author

thomaseizinger commented Sep 12, 2023

Side-note: Such an oppinonated behaviour could also be useful for low-level testing of protocols because we can plug the real implementation in on one end and use this behaviour on the other end where we get direct access to the stream.

@p0mvn
Copy link
Contributor

p0mvn commented Sep 23, 2023

I'm sorry, I haven't been able to put in the hours into investigating this, and I don't foresee being able to in the next few weeks. Unasigning myself for now.

If still open, I will pick this up later

@p0mvn p0mvn removed their assignment Sep 23, 2023
@thomaseizinger
Copy link
Contributor Author

I'm sorry, I haven't been able to put in the hours into investigating this, and I don't foresee being able to in the next few weeks. Unasigning myself for now.

If still open, I will pick this up later

No worries at all, thanks for being proactive :)

@eserilev
Copy link
Contributor

eserilev commented Nov 7, 2023

id like to pick this up

@thomaseizinger
Copy link
Contributor Author

That would be great actually! Coincidentally, I just bumped this to the top of my priority list this morning and was about to assign myself 😄

I think it is something that will really benefit our users.

Since I wrote the above description, I slightly changed my mind on what the API should be after talking to @mxinden. Instead of taking a handler function, I think we should return a struct that is called IncomingStreams:

impl IncomingStreams {
	pub async fn next(&mut self) -> (PeerId, Stream);
	pub fn poll_next(&mut self) -> Poll<(PeerId, Stream)>;
}

impl Behaviour {
	pub fn new(protocol: StreamProtocol) -> (Self, Control, IncomingStreams);
}

The advantage of the above API is that it supports backpressure and if users want, they can always implement their own loop where they take a handler and call it with every stream.

With this in mind, the goal of the example should be:

  • To serve as a "copy-paste"-able example for users that "just want access to a stream". There are many ways in how this can be implemented and what is "good" will be very specific to a user's needs. Thus, it is kind of hard to offer this as a proper library component, hence we provide it as an example for a starting point that users can fork and modify.
  • To document how to implement your own NetworkBehaviour.

Let me know if this makes sense and whether you need any more guidance :)

@mergify mergify bot closed this as completed in #5027 Jan 16, 2024
mergify bot pushed a commit that referenced this issue Jan 16, 2024
For a while now, `rust-libp2p` provided the `request-response` abstraction which makes it easy for users to build request-response based protocols without having to implement a `NetworkBehaviour` themselves. This PR introduces an alpha version of `libp2p-stream`: a `NetworkBehaviour` that directly gives access to negotiated streams.

In addition to complementing `request-response`, `libp2p-stream` also diverges in its design from the remaining modules by offering a clonable `Control` that provides `async` functions.

Resolves: #4457.

Pull-Request: #5027.
@thomaseizinger
Copy link
Contributor Author

This is now released!

https://github.com/libp2p/rust-libp2p/releases/tag/libp2p-stream-v0.1.0-alpha

umgefahren pushed a commit to p2p-industries/rust-libp2p that referenced this issue May 20, 2024
For a while now, `rust-libp2p` provided the `request-response` abstraction which makes it easy for users to build request-response based protocols without having to implement a `NetworkBehaviour` themselves. This PR introduces an alpha version of `libp2p-stream`: a `NetworkBehaviour` that directly gives access to negotiated streams.

In addition to complementing `request-response`, `libp2p-stream` also diverges in its design from the remaining modules by offering a clonable `Control` that provides `async` functions.

Resolves: libp2p#4457.

Pull-Request: libp2p#5027.
umgefahren pushed a commit to p2p-industries/rust-libp2p that referenced this issue May 20, 2024
For a while now, `rust-libp2p` provided the `request-response` abstraction which makes it easy for users to build request-response based protocols without having to implement a `NetworkBehaviour` themselves. This PR introduces an alpha version of `libp2p-stream`: a `NetworkBehaviour` that directly gives access to negotiated streams.

In addition to complementing `request-response`, `libp2p-stream` also diverges in its design from the remaining modules by offering a clonable `Control` that provides `async` functions.

Resolves: libp2p#4457.

Pull-Request: libp2p#5027.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants