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

WebRTC exploration #396

Merged
merged 64 commits into from
Feb 3, 2023
Merged

WebRTC exploration #396

merged 64 commits into from
Feb 3, 2023

Conversation

ScottyPoi
Copy link
Collaborator

@ScottyPoi ScottyPoi commented Dec 16, 2022

WebRTC

PR to explore WebRTC use in Transport Layer

Hybrid Transport Service

Browser clients can now use a hybrid transport layer. This adds the capacity to connect directly to other browser-clients.

  • HybridTransportService
    • this.webRTC = waku / webrtc tranpsort
    • this.websocket = websocket transport

LibP2P / Waku

  • Browser clients may discover and set up connections using the WAKU protocol.
  • In the transport layer, a Waku-LightNode "subscribes" to its NodeId.
  • Messages pushed via Waku LightPush
    • will be encoded and decoded using the client's NodeId
      • using @waku/interfaces Encoder/Decoder
    • Nodes initiate connections by pushing a serialized discv5 PING packet to the peer's NodeId on the WAKU LightPush protocol
    • Nodes subscribe to their own NodeId on LightPush protocol. Discv5 packets received this way are handled using transport.emit('packet', ...) just like those received any other way.
  1. The PortalNetwork (PING/PONG) & Discv5 authentication packets (whoareyou, challenge, handshake) are exchanged using WAKU LightPush protocol.
  2. WebRTC "handshake" packets (Offer/Answer/ICE) are exchanged using WAKU LightPush protocol.
  3. All further messages are exchanged directly p2p (browser-to-browser) using WebRTC protocol.

The WAKU LightPush part was built using core @waku and @libp2p packages.
The WebRTC part was built from scratch using mozilla web api (https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API)

#### ENR
To advertise that a node is available for RTC connections, we add 1 byte to the ENR like ENR.set('rtc', 0x01). When sending a packet, we look for this bit in a peer's ENR, and then route the outgoing packet either to an rtc datachannel, or to the websocket proxy.

This part was removed as unnecessary
Embedding a webrtc tag in the ENR does not fully provide a way to route outgoing packets in all scenarios, so this part was dropped. The transport layer does not have access to the discv5 routing table, nor does it directly interact with ENR. This also failed to address scenarios where we have received a packet from a peer who's ENR is not yet known (1st PING).

Instead, the first time the hybrid transport handles an incoming packet from a new Multiaddr, it will Map that multiaddr to the type of transport (websocket vs. rtc) from which it was received. The hybrid "Send" function will check that map to determine how to send packets to that peer.

Without any RTC tag in the ENR...
When we PING an unknown node we will not yet know which transport it supports. In this case we will send the PING packet out along both transports. When a response is received, we then know which transport (websocket or webrtc) this peer supports.

The PING is sent first over WAKU to prioritize webrtc connections among hybrid Ultralight nodes.

Proxy / Multiaddr

The websocket proxy has been edited to listen for an event emitted from the connecting websocket to set the port number. This way, the udp port number can be selected during PortalNetwork.create(), instead of randomly by the Proxy.

CLI

Small edits made to fix some build issues. A CLI client can now connect to a browser-client running in normal mode.

Browser

Small layout changes. Header now displays address, nodeId, and ENR.
Routing table display will distinguish between RTC peers and websocket peers. RTC connected peers will be highlighted a different color, and have the RTC tag in the last column.

Issue: routing table view does not reliably display all peers
Two browsers may connect, but both may not appear in the display
They are connected. The issue is in the react components

To test

  1. OPTIONAL From the root directory - npm run start-proxy
  2. From the root directory - npm run start-browser-client
  3. Open 2 browser pages to localhost:8080
  • If the PROXY is running
    - clients should start and connect to external bootnodes, quickly building a routing table
  • If the PROXY is not running
    • clients will fail to connect to external bootnodes
  1. COPY ENR from one browser, and paste into connect to peer input on other page.

clients should connect over waku, perform discv5 handshake, perform webrtc handshake, and add eachother to their routing tables
You can now test using the Peer-Tools or Get-Block-By functions in the browser

@@ -43,8 +44,14 @@ export default function Layout() {

const layoutVariant = useBreakpointValue({
base: (
<Tabs height="100%" width={'100%'} index={state.tabIndex} onChange={handleTabsChange}>
<TabList height="5%" width="100%" justifyContent={'space-around'}>
<Tabs width={'100%'} index={state.tabIndex} onChange={handleTabsChange}>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Quick note that the simple chat tab is only visible in a mobile equivalent view.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I reverted all of the browser layout changes -- they were just a part of the process for me figuring things out

Copy link
Collaborator

@acolytec3 acolytec3 left a comment

Choose a reason for hiding this comment

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

The biggest thing I'm wondering about is do we need to have the webrtc stuff embedded in the client and protocol? We're kind of breaking the modular transport concept by introducing all these special checks for webRTC in client and protocol and ideally all of that would be handled within the hybridTransport class. I've offered some ideas in my comments so let me know if there's a way I can further help.

@@ -54,10 +52,17 @@
"@ethereumjs/common": "^3.0.0",
"@ethereumjs/tx": "^4.0.0",
"@ethereumjs/util": "^8.0.0",
"@libp2p/bootstrap": "6.0.0",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are all these now devDependencies? Don't we need them installed in order to use the waku/libp2p stuff?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved them all up to dependencies. I'll admit that my grasp of the difference between dependencies and devDependencies is a bit fuzzy...

@@ -25,7 +25,7 @@ jobs:
with:
node-version: 16
cache: 'npm'

- run: npm i -g @mapbox/node-pre-gyp
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the purpose of this build step? Is there some browser dependency that needs node-gyp or is this leftover from when you were using the older webtorrent tracker library?

toHexString(enr.get('rtc')!) === '0x01'
) {
this.logger.extend('RTC')('Adding RTC conneection')
await this.client.discv5.sessionService.transport.connectWebRTC(enr.nodeId)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would it be possible to move this whole thing down to the transport layer somehow? Ideally, the protocol layer stuff shouldn't need to worry about which transport layer we're using. Could you move the webRTC maps up from webRTC.ts to hybridTransport.ts and then use the multiaddr or nodeId passed into client.discv5.session.transport.send to figure out whether to go with webRTC or websocket->UDP?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, in fact this was not necessary here at all, as it is happening in the transport itself now. Deleting these lines should not have an affect

this.webRTC.closeAll()
}

async send(to: Multiaddr, toId: string, packet: IPacket, rtc?: boolean) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

As noted in my other comment, instead of adding a new parameter to the transport interface for send here, can we just keep a Map<nodeId, webRTCcontactinfo> inside this class and determine whether to use webrtc or websockets here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, agreed. I tried several solutions to the "PING from unknown node" problem, and that rtc parameter was one of the clunkier ones. the webrtc transport does already keep a map of nodeId > RTCConnection.

Comment on lines 28 to 29
- run: npm i -g @mapbox/node-pre-gyp
- run: npm i
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think this can be removed. I mentioned this at some point, but a lot of older webrtc npm packages (simple-peer, etc.) use a package called wrtc, which will not build without this node-pre-gyp thing installed in the system. I'm pretty sure I've removed anything that referenced that package, though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed. deleted those changes and the build works fine.

@ScottyPoi
Copy link
Collaborator Author

@acolytec3

The cli-devnet-integration test is replacing the previous integration suite. it can probably be expanded, but I think it is sufficient.

My own testing hasn't exposed any issues with the webrtc transports, so I'd be confident merging this with your approval.

@acolytec3
Copy link
Collaborator

@acolytec3

The cli-devnet-integration test is replacing the previous integration suite. it can probably be expanded, but I think it is sufficient.

My own testing hasn't exposed any issues with the webrtc transports, so I'd be confident merging this with your approval.

Fine by me. I added node-pre-gyp to the cli dependencies since npm i doesn't work locally otherwise. It's going to be a hard requirement for anyone trying to build the cli version of the client going forward.

@ScottyPoi
Copy link
Collaborator Author

Fine by me. I added node-pre-gyp to the cli dependencies since npm i doesn't work locally otherwise. It's going to be a hard requirement for anyone trying to build the cli version of the client going forward.

does that work??

i found that i needed node-pre-gyp installed globally, before doing npm i. This is certainly not ideal, i know. I tried hard to find a webrtc package that did not have wrtc somewhere in the dependency tree, but alas.

It may just look like me building it from scratch in typescript. maybe i'll get famous on npm

Copy link
Collaborator Author

@ScottyPoi ScottyPoi left a comment

Choose a reason for hiding this comment

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

Ah, I should have explained --

the cli npm run test:integration triggers the previous integration test adapted for the nodejs client.
While this test does pass locally, it never worked (wouldn't actually run) in the CI checks.

The other model that was already in place, the "devnet-cli-test", has always worked, and I think as it is now, it does cover the same ground as the old integration tests

@ScottyPoi
Copy link
Collaborator Author

@acolytec3
any more thoughts on this?
i removed the webrtc stuff from CLI, and all of the wrtc / node-pre-gyp stuff along with it.

Copy link
Collaborator

@acolytec3 acolytec3 left a comment

Choose a reason for hiding this comment

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

Awesome work! We still need to work on the race conditions in the contentWriter but this can be addressed later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants