Creating a libp2p Network and Connecting with Each Other Using JS via WebRTC (Theory)
The previous theoretical article introduced the principles of creating a libp2p network and connecting with each other using JS via WebRTC. This blog will gradually introduce how various features in the theory can be practically implemented.
Preparing the Project#
The practice uses the official libp2p-webrtc-guide project, which provides a relay node project and a libp2p web project that can interact in the browser. First, git clone this project and install the dependencies.
#git clone the project
git clone https://github.com/libp2p/libp2p-webrtc-guide
#install npm dependencies
cd libp2p-webrtc-guide
npm install
Starting the libp2p Relay Node#
In the theoretical article, it was mentioned that the libp2p Relay node has two roles in the network: one is the Circuit Relay V2 role, which helps other nodes in the network discover each other and forward traffic. The other is the PubSub Peer Discovery role, which helps nodes connect automatically. The project provides a JS implementation of the Relay. Enter the following command to start the libp2p Relay node.
#start the libp2p relay node
npm run start:relay
The above command essentially runs the src/relay.js file in a node environment. Let's take a look at the relay.js file.
async function main() {
// enable('*')
const libp2p = await createLibp2p({
addresses: {
listen: [
'/ip4/0.0.0.0/tcp/9001/ws',
'/ip4/0.0.0.0/tcp/9002',
],
},
transports: [
webSockets(),
tcp(),
],
connectionEncryption: [noise()],
streamMuxers: [yamux()],
connectionGater: {
// Allow private addresses for local testing
denyDialMultiaddr: async () => false,
},
services: {
identify: identify(),
autoNat: autoNAT(),
// Configured the two most important roles for the Relay node.
relay: circuitRelayServer(),
pubsub: gossipsub(),
},
})
libp2p.services.pubsub.subscribe(PUBSUB_PEER_DISCOVERY)
console.log('PeerID: ', libp2p.peerId.toString())
console.log('Multiaddrs: ', libp2p.getMultiaddrs())
}
main()
The above code creates a libp2p node, configured to listen on ports 9001 and 9002 on all addresses, where 9001 uses the WebSocket protocol. It defines the encryption method as noise and defines yamux as the stream multiplexing method. The most important part is the configuration of the Relay node's key CircuitRelayServer and gossipsub information in the services. It also subscribes to the topic represented by the variable PUBSUB_PEER_DISCOVERY. Finally, it prints the PeerID information and Multiaddrs information of the Relay node. After running the Relay node, the console prints the following information:
> node src/relay.js
PeerID: 12D3KooWDpJEwVdPBrQf6ZcwRYPzynBU2PZNGTQs3X8uh6FpxRTZ
Multiaddrs: [
Multiaddr(/ip4/127.0.0.1/tcp/9001/ws/p2p/12D3KooWDpJEwVdPBrQf6ZcwRYPzynBU2PZNGTQs3X8uh6FpxRTZ),
Multiaddr(/ip4/172.30.63.206/tcp/9001/ws/p2p/12D3KooWDpJEwVdPBrQf6ZcwRYPzynBU2PZNGTQs3X8uh6FpxRTZ),
Multiaddr(/ip4/192.168.5.101/tcp/9001/ws/p2p/12D3KooWDpJEwVdPBrQf6ZcwRYPzynBU2PZNGTQs3X8uh6FpxRTZ),
Multiaddr(/ip4/0.0.1.1/tcp/9001/ws/p2p/12D3KooWDpJEwVdPBrQf6ZcwRYPzynBU2PZNGTQs3X8uh6FpxRTZ),
Multiaddr(/ip4/127.0.0.1/tcp/9002/p2p/12D3KooWDpJEwVdPBrQf6ZcwRYPzynBU2PZNGTQs3X8uh6FpxRTZ),
Multiaddr(/ip4/172.30.63.206/tcp/9002/p2p/12D3KooWDpJEwVdPBrQf6ZcwRYPzynBU2PZNGTQs3X8uh6FpxRTZ),
Multiaddr(/ip4/192.168.5.101/tcp/9002/p2p/12D3KooWDpJEwVdPBrQf6ZcwRYPzynBU2PZNGTQs3X8uh6FpxRTZ),
Multiaddr(/ip4/0.0.1.1/tcp/9002/p2p/12D3KooWDpJEwVdPBrQf6ZcwRYPzynBU2PZNGTQs3X8uh6FpxRTZ)
]
Each libp2p node has a unique PeerID, which is automatically generated by calling identity() in the Services configuration. If a specific identity is not specified, a different PeerID will be generated each time.
The console also prints 8 addresses, which are the multiaddrs of this libp2p node, corresponding to all the IPv4 addresses on the local machine, using WebSocket on port 9001 and TCP on port 9002.
Starting the Browser Node#
At this point, the libp2p Relay node has been successfully started. Next, we can start the browser node and attempt to connect to the libp2p Relay node. Open a new terminal and run the following command, then open the browser to create a new browser node.
npm run start
However, if you open the browser at this point, you will find that the PeerID of the node is Unknown. Opening the browser console will reveal the following error:
CodeError: Service "@libp2p/webrtc" required capability "@libp2p/circuit-relay-v2-transport" but it was not provided by any component, you may need to add additional configuration when creating your node.
at checkServiceDependencies (components.ts:173:15)
at new Libp2pNode (libp2p.ts:193:5)
at createLibp2pNode (libp2p.ts:423:10)
at async createLibp2p (index.ts:167:16)
at async App (index.js:19:18)
(anonymous) @ index.js:121
Promise.catch (async)
(anonymous) @ index.js:120
(anonymous) @ index.js:122
Based on the error message, it can be determined that @libp2p/webrtc requires support from @libp2p/circuit-relay-v2-transport. According to the previous theoretical article, libp2p needs to use Circuit Relay V2 to achieve WebRTC P2P connections, and the two are inseparable. Open the src/index.js file, which configures the browser's libp2p node information. In the createLibp2p method's transports configuration, add support for @libp2p/circuit-relay-v2-transport to the libp2p node.
transports: [
webSockets({
// Allow all WebSocket connections including without TLS
filter: filters.all,
}),
webTransport(),
webRTC({
rtcConfiguration: {
iceServers: [
{
// STUN servers help the browser discover its own public IPs
urls: ['stun:stun.l.google.com:19302', 'stun:global.stun.twilio.com:3478'],
},
],
},
}),
// 👇 Required to create circuit relay reservations in order to hole punch browser-to-browser WebRTC connections
// Add support for @libp2p/circuit-relay-v2-transport
circuitRelayTransport({
discoverRelays: 1,
}),
]
Now, when you open the browser, you will find that the browser can successfully create a libp2p node, and each refresh will generate a different PeerID.
According to the theoretical article, for the browser to join the libp2p network, it needs to rely on the Relay node. Therefore, the first step for the browser is to connect to the Relay node. In the open browser page, enter any multiaddr address of the Relay node (note that it needs to be a WebSocket-supported address; according to the theoretical article, the browser and the Relay node establish a connection via WebSocket), click connect, and upon successful connection, you will see the connection information.
Observing the information in the browser, you can see that the browser has successfully connected to a node using WebSockets and provided the multiaddr of this browser. Looking at the multiaddr, you can see that it consists of two parts: the first half is the multiaddr of the Relay node, and the second half is /p2p-circuit/p2p/ followed by this node's PeerID.
After successfully connecting one browser to the Relay server, you can try using a new browser node to connect to it, forming a libp2p network consisting of two browsers and one relay node.
Open a new browser, which will generate a different PeerID from the previous browser. Enter the multiaddr of the original browser in the input box and click connect. At this point, you will find that the new browser can not only directly link to the old browser but also successfully connect to the Relay node, showing that the Circuit Relay connection count is 1. If you close the new browser at this point, the old browser will quickly display that it has disconnected from the new browser.
At this point, the connection between the browsers has been successfully established. However, each time the browser is opened, it requires manually entering the address to connect to the Relay node. To solve this problem, you can configure the bootstrap in the peerDiscovery configuration of the createLibp2p method in src/index.js to the Relay node address.
peerDiscovery: [
bootstrap({
list: ['WebSockets address of the Relay node'],
}),
Then, when you open a new browser, you will find that the browser node will automatically connect to the Relay node.
Starting the WebRTC Connection#
The connection process diagram in the theoretical article explains that the browser node will first discover the browser node through Circuit Relay V2 and then begin to establish a standard WebRTC connection. In the above steps, we have successfully connected the browser using Circuit Relay V2. Next, we will start WebRTC connection support.
In src/index.js, add the listen address to the addresses of the createLibp2p method.
addresses: {
listen: [
// 👇 Listen for webRTC connection
'/webrtc',
],
}
After adding this, when you open a new browser and connect to the Relay node, you will find that the number of multiaddrs for this browser has doubled. Observing the additional multiaddr, you can see that the new address has added a /webrtc compared to the original address, indicating that WebRTC support has been added to these addresses.
Similarly, open a new browser and try to connect to the old browser using addresses that support both WebSockets and WebRTC. After completing the connection, you will find that the WebRTC connection count has become 1, indicating that a WebRTC connection has been successfully established between the two browsers.
Configuring PubSub Peer Discovery#
The theoretical article explains that the libp2p Relay node has two functions: one is Circuit Relay V2, which solves the feasibility of connections between nodes, and the other is PubSub Peer Discovery, which solves the automatic connection between nodes.
In this practice, GossipSub is used to achieve automatic connection between nodes by subscribing to the same topic and broadcasting to connected nodes.
In the src/index.js file, configure pubsubPeerDiscovery and gossipsub for the node.
peerDiscovery: [
bootstrap({
list: ['/ip4/127.0.0.1/tcp/9001/ws/p2p/12D3KooWKsQgy75zYnHWqoMw4fgE9R7FmjzFDD8grnsjABuXtAoN'],
}),
// New configuration, subscribe to the topic
pubsubPeerDiscovery({
interval: 10_000,
topics: [PUBSUB_PEER_DISCOVERY],
}),
],
services: {
// New configuration, add Gossipsub service
pubsub: gossipsub(),
identify: identify(),
},
Finally, create multiple browsers without any operations, and after a while, the browsers will automatically connect to each other. Thus, a basic usable libp2p network established between browsers using JS via WebRTC has been successfully created.