Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.Sign up
Currently we describe the various types of capabilities as different node types, such as bootstrap nodes, mailserver servers, mailserver clients, relay nodes, and light nodes. In some places we describe these as actual capabilities that different things a node can do. This inconsistency and lack of clarity around a node and what it can do makes it harder to do many things. Among others, to reason about and extend things related to specs, analysis, clients, simulations, running a node and explaining things to end users.
A cleaner mental model is to be consistent about it being a node with a set of capabilities. The "X node" then becomes more of a shortcut to refer to certain optimized client configurations. This is more in-line with the adaptive nodes ethos that we want to practice, as well as future enhancements we want to make (libp2p, dagger). It is conceptually simpler and more accurate. If clients implement this correctly, it also simplifies the use of different capabilities for people who want to run their own node.
Use the following terminology consistently throughout the spec:
We should also distinguish between these capabilities as outlined above, and what are called "RLPx subprotocol capabilities" in devp2p. To me the naming above seems accurate, but open to other ideas to not confuse the two.
An important follow up question is in terms of how we communicate these capabilities and act on them. For devp2p, this is done in the Hello handshake as a list of protocols we handle.
Looking at libp2p, which has way better protocol negotiation / multiplexing support than devp2p, it works similarly conceptually but with more flexibility. Essentially we define a string such as
There appears to be no specific support for communicating configurations of a protocol other than as subprotocols or version numbers. Unless we want to do this as an "inner negotiation", it seems to me that specifying separate subprotocols is the simplest. Open to other ideas here.
What do we care about? If I'm sending a Waku message, I want to make sure the node I'm connecting to has the ability to relay messages. If I'm receiving historical messages I want to make sure the node I'm asking stores messages. To me this would suggest a protocol selection as follows: [
What do people think?
@oskarth Great plan to define terminology on types of nodes and their capabilities!
I think ideally there should be a clear definition & name for each actual use case of a node. So starting from that would perhaps be a good approach.
Some changes I would make:
If "selective" is not clear enough specific names could be given here for these nodes or it could also just be simplified by limiting whether if a specific node type can be or must be selective based on the use cases that exist, e.g. for a light node, it would typically only make sense to be selective, and probably selective based on list of topics (in future). So that could be within the definition of light node to simplify things, but it doesn't have to be.
Some example use cases for the types of node:
Ideally (some of) these are adaptive capabilities that a user can change based on either their privacy needs or the current available power/bandwidth.
Most simple form could then be e.g.:
Regarding the communicating of configurations, I've thought about this when discussing #41 and also looked before briefly into the libp2p information linked. And while this is very useful to negotiate protocols and version of protocols, I believe announcing the actual "role" within the protocol will still be required through a typical hello/status message. E.g. for a mail server, the store protocol would still require the client that requests that data and the type of client that responds with a set of data. But perhaps I'm not fully understanding the idea here or how this works in libp2p.
In the latest discovery protocol (v5), there are two ways to discover nodes:
I'm sharing the above, just to highlight the subtle difference between "supported protocols" and "data being served" when one thinks about capabilities. The protocol negotiation mechanism in DevP2P and LibP2P covers only the aspect of supported protocols and it comes with one additional restriction - the capabilities are assumed to be symmetric. If you support the light client protocol for example, it's assumed that you'll be able to respond to server-side requests even when you are just a client.
Anyway, if you break the Waku protocol into smaller pieces, you'll be able to write code along these lines:
if peer.supports(WakuRelay): peer.sendMessage(...) # These are hypothetical protocol messages # Sorry for not using the real ones if peer.supports(WakuStore): peer.fetchFromStore(...) # These are hypothetical protocol messages # Sorry for not using the real ones
The alternative is to exchange the list of capabilities in the handshake message (status), to remember them in the
if peer.supports(Waku) and WakuStore in peer.protocolState(Waku).capabilites: ...
This approach will be able to support more arbitrary logic related to the capabilities, but you are likely going to lose the type 1) discovery mechanism.
I like the idea of capabilities, but I wouldn't (only if informally), try to associate names with groups of capabilities. A capability is some well defined, self contained functionality - for example
Yes, I think this makes sense, but there is more than one way of doing it.
My recommendation is to stick to well defined protocol string that identify a specific functionality, something like what @oskarth outlined above, as well as communicating capabilities either as part of the
I like the idea of a well defined protocol string which includes protocol name and version. This is a very simple model which can be supported by even more naive peers discovery protocols.
This is also fine. Alternatively,
Each peer also must share its capabilities/data being server and it should be possible to traverse through the peers list for a given protocol in order to select only these that supports a given capability -- just like @zah described it.
It means that each type of node must support all packet codes within a given protocol but the response might be
Exchanging capabilities in a handshake proved to be working fine but extra care needs to be taken to make sure the handshake is backward and forward compatible. A separate message to update the capabilities also sounds good but I would reserve a single packet to do that instead of one packet per capability like the current Whisper spec describes it.
In terms of peers discovery mechanism, I think it's a separate problem. A situation when we decide that we match by the protocol string and capabilities are exchanged in the handshake should still allow a node to register itself in the discovery mechanism using either capabilities and/or topics. If a peers discovery protocol is very generic, the spec must describe how to register in each of these discovery protocols.
might want to keep these simple/opaque - the more complicated they get (ordering, semver), the harder it is to create a forward-compatible upgrade because other (older) clients in the wild will have complex behaviours.
a dumb opaque string that's matched exactly is generally the easiest to deal with - clients then generally implement a facade that simulates old versions they still support and have the logic of deciding protocol priorities locally in them if there are several similar protocols that do the job (instead of using the version string for this). in this world, versions don't exist really, only completely separate protocols that happen to have some similarities.
there's a lot of juicy info in https://github.com/ethereum/eth2.0-specs/blob/dev/specs/networking/p2p-interface.md#design-decision-rationale (edit: new link https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/p2p-interface.md) - the eth2 networking spec on top of libp2p - during the development of that spec, many of the same questions were posed.
the critical difference here is how much you think you know about a peer before connecting to them - rich discovery allows you to not connect to uninteresting peers, but carries a cost in terms of a more expensive peer discovery dht. you can generally get peer information from many sources: dht, lan, gossip between already connected peers, connect-and-negotiate etc etc - ideally, the information content from all these sources is the same but practicalities might make some richer than others requiring peers to act on incomplete information (ie dht's often operate over udp that has a datagram size limit)
this is an interesting point, though one can usually deal with this in an in-protocol negotiation (again, it's all about how early you can discard a peer as uninteresting)
the simple way is to disconnect and reconnect - because you are now effectively a different peer with different capabilities - generally, this is supposed to be fairly cheap in a p2p system. fancy reconnection messages and strategies sound like something to leave a later version if it becomes a actual problem.