-
Notifications
You must be signed in to change notification settings - Fork 930
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
make it possible to listen to some events in a derived NetworkBehaviour #2485
Comments
I am not sure I follow. You want to be able to override the generated implementation of |
Given the current structure, it would be more consistent if the generated implementation would be extended to call the provided associated function. This way sub-behaviours keep working as they were but the higher-level behaviour can still see the events it needs to see. If we allow the implementation to override the behaviour hook, then the higher-level behaviour could filter the events for its sub-behaviours — for better or worse. To my mind it would be a worthy goal that a NetworkBehaviour works as an encapsulated black box, to be reused in larger behaviour hierarchies. This requires a perfectly balanced API surface so that code on the exterior can do many useful things without being able to break the code on the interior. |
If I understand your suggestion correctly, this could e.g. be useful in rust-libp2p/protocols/autonat/src/behaviour.rs Lines 462 to 499 in 861e15d
|
I agree that this would be useful. But in AutoNAT there are quite a lot of methods with custom implementation, so doing this via the suggested trait WrapperBehaviour {
type Inner: NetworkBehaviour;
fn inner(&mut self) -> &mut Self::Inner;
// Mirror all NetworkBehaviour methods with empty body.
// Implementors can overwrite them if they want a custom implementation.
fn inject_new_listener(&mut self, id: ListenerId) {}
..
}
impl<T: WrapperBehaviour> NetworkBehaviour for T {
fn inject_new_listener(&mut self, id: ListenerId) {
self.inject_new_listener(id);
self.inner().inject_new_listener(id);
}
..
} Behaviours like AutoNAT could then implement I did not think this through yet though. What do you think? |
Yes, @elenaf9 that is going in the direction I’ve been pondering since opening this issue. What I’d like to have is the ability to compose behaviours as mostly black boxes — and what I couldn’t yet figure out is how large the deviation from “black” will need to be. In the metaphor, we’d need at least some non-black lettering to state the purpose and abilities of the box :-) Here’s my current train of thought, which has not yet reached its destination. A network behaviour represents the ability to trigger something to happen inside a swarm (whereby I mean multiple nodes). This could be moving data around, keeping tabs on who’s who, gossiping some (future) information, etc. The current NetworkBehaviour macro solves one part of this problem, namely the case where the local node confidently presumes the capabilities of its environment. I seek to go one step further: there are multiple ways of actually performing that “something” in the swarm, since protocols evolve over time, and each node probably knows several of these ways. The strength I see in libp2p is that it can serve as a matchmaker, selecting the preferred option from the intersection of capabilities of peers. In this sense I want to compose behaviours. It is clear that different purposes require different forms of composition. For example gossiping may well opt to try all known mechanisms to ensure that the message gets spread while moving data around may try multiple ways of finding the data but only one to move it (to not waste bandwidth). And making a request to a single peer should make it only once, choosing the preferred mechanism. My preliminary conclusion from this is that we’d need something like the generic derived composition of sub-behaviours like @elenaf9 describes, to keep the black boxes functioning normally while they are in fact composed. The second part we’d need is a controller that can influence the composition. For this purpose, it will need to have access to the inputs and outputs of the sub-behaviours. The degree to which this controller will need to understand the inner workings of the composed behaviours will depend on the form of composition. It could be necessary to filter the messages sent from behaviour to protocol handler, for example. As far as I can see, the infrastructure for this is already in place, it is just inconvenient to use (as in: needs a lot of boilerplate code). Sorry for not directly answering your question, Elena, but before I can comment on API I need to better understand the nature of the problem myself. Your proposal feels like it is going in the right direction, though. |
Sunday morning, cold and bright weather, out in the countryside, taking a walk … and I finally understood:
This has implications on my desire of protocol reuse sketched above. I now think that my current use-case — namely old and new variants of a streaming response protocol — is not a behaviour concern, it is a protocol concern and should be encapsulated in the ProtocolsHandler: the unit of reuse needs to be the sub-protocol, not the sub-behaviour. With this in mind, I see the following behaviour classes:
While the last one is by design bespoke, the first two could be implemented generically on the behaviour level, parameterised over ProtocolsHandlers and some configuration. Then, on the protocol level, it should be possible to compose protocols offering essentially the same shape of communication in a generic fashion, with automatic negotiation for each pair of peers. This approach would make the original point of this issue moot. What do you think @mxinden @thomaseizinger @elenaf9? How would AutoNAT fit into this? |
From the list above, I'd say it falls into I have thought a lot about this so I would like to share a few more thoughts. The main driving forces of the current design in my observation are:
The performance aspect drives the separation into two interfaces which introduces the event-driven communication and separated state. What we can currently not support particularly well with these abstractions are: Protocols that don't have any cross-peer state/logic (Which is not surprising, given that the design is optimized FOR these). In absence of cross-peer state/logic, the Circling back to this original issue: I think the deeper issue at hand here is that not every protocol needs all of the 3 above mentioned characteristics. Some protocols (like request-response) would be much better served if they wouldn't need to go though the If getting access to the substreams would be easier, then the need for composing a Shameless-plug: I've experimented with an idea in regards to this here: rust-libp2p/protocols/rendezvous/src/substream_handler.rs Lines 175 to 185 in 861e15d
What this essentially does is create a reusable rust-libp2p/protocols/rendezvous/src/client.rs Lines 238 to 248 in 861e15d
There are obviously multiple ways of how we can tackle this but it is my overall opinion that the current design of |
continued at #2938 |
Yesterday I had a case where deriving the behaviour was perfect apart from one line — I needed to stay informed of peers disconnecting. With the ability to expand the proc macro in VS Code this was a matter of seconds, but it would be nicer for readers of the code and for maintainability if this patch could be formulated more elegantly, for example with syntax like
The text was updated successfully, but these errors were encountered: