Skip to content

ETH/LES Protocols - Simplify all the things #2939

Open
@acolytec3

Description

I've been complaining about this for long enough so it's time to put down the issue I see with our network stack and see if we can agree on a best way forward.

Our current Devp2p Protocol stack is a complete mess with layers of inheritance and bound functions and classes that obscure what's actually happening and completely breaks intellisense and the ability to jump to type/function definition. As a starting point, what I'm proposing is that we move all of the ETH/LES protocol handlers from the client to devp2p. The biggest issue here is something like below:
The message handlers and encoding/decoding for each protocol (ETH/LES/SNAP) is handled inside the client in a Protocol method that extends BoundProtocol which is connected to the underlying devp2p send/receive functionality via bound methods. Because the top level message handlers inside the client's ETH protocol class (which extends a BoundProtocol) which is has a devp2p RLPx network class embedded inside it with the RLPx send method then bound back up to the BoundProtocol, there is no straight-forward way to go step through the traces of the call stack for debugging purposes without much agony trying to track the message flow.

So, what I'm envisioning is something like completely doing away with the Protocol/BoundProtocolparent classes and then moving all of the logic for ETH/LES protocol message encoding/decoding/sending into devp2p. I'm not sure I can put pseudocode down at the moment to describe it but I'd love to have the client explicitly instantiate an ETH Protocol, an LES Protocol, and a SNAP protocol (or whichever subset it is running) where all the Protocol classes are housed in devp2p. So, when the client needs to announce new transactions, it just calls devp2p.eth.newPooledTxHashes and passes in the list of tx hashes (or Eth68 equivalent) to send and then devp2p takes care of serializing and sending the message.

The main things I want to get rid of are instances where we do are binding essentially unknown functions to timers which then makes it nigh impossible to figure out what's going on later on (basically this sort of thing in BoundProtocol)

    return new Promise((resolve, reject) => {
      resolver.timeout = setTimeout(() => {
        resolver.timeout = null
        this.resolvers.delete(message.response!)
        reject(new Error(`Request timed out after ${this.timeout}ms`))
      }, this.timeout)
      resolver.resolve = resolve
      resolver.reject = reject
    })

Ideally this gets replaced with explicitly names message handlers in a new one size fits all EthProtocol class inside of devp2p.

return new Promise(resolve, reject) => {
  pooledMessages = setTimeout(() => this.newPooledTxnHashes(resolve, ...otherParams), this.timeout)
  reject(new Error('request timed out)'))
})

Any thoughts on this? I'm sure I'm missing some of the complexity of why things were done the way they currently are but I'm game to take a deep dive on this and try and reason it out.

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions