使用JS通过WebRTC创建libp2p网络并相互连接（实践）

使用 JS 通过 WebRTC 创建 libp2p 网络并相互连接（理论）
在上一篇理论的文章中介绍了使用 JS 通过 WebRTC 创建 libp2p 网络并相互连接的原理，在这篇博客中会逐步介绍理论中的各种特性如何具体实践。

准备项目

实践使用的是官方提供的 libp2p-webrtc-guide 项目，这个项目提供了一个 relay 节点项目，以及一个可以在浏览器中进行交互的 libp2p 网页项目，首先 git clone 这个项目并安装依赖

Copy
#git clone 项目
git clone https://github.com/libp2p/libp2p-webrtc-guide
#安装npm依赖
cd libp2p-webrtc-guide
npm install

启动 libp2p Relay 节点

在介绍理论的文章中说到，libp2p Relay 节点在网络中有两个角色，一个是 Crituit Relay V2 角色，帮助网络中的其他节点发现对方，并转发流量。还有一个是 PubSub Peer Discovery 角色，帮助节点之间自动连通。该项目中提供了 JS 的 Relay 实现，输入以下指令启动 libp2p Relay 节点。

Copy
#启动libp2p relay节点
npm run start:relay

以上指令实质上是以 node 环境运行了 src/relay.js 文件，观察 relay.js 文件

Copy
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(),
// 配置了Relay节点最重要的两个角色。
relay: circuitRelayServer(),
pubsub: gossipsub(),
},
})

libp2p.services.pubsub.subscribe(PUBSUB_PEER_DISCOVERY)

console.log('PeerID: ', libp2p.peerId.toString())
console.log('Multiaddrs: ', libp2p.getMultiaddrs())
}

main()

以上代码创建了一个 libp2p 节点，配置监听在所有地址的 9001 端口和 9002 端口，其中 9001 使用 WebSocket 协议，定义了加密方式为 noise，定义了 yamux 作为流复用方式，其中最重要的是在 services 中配置了 Relay 节点关键的 CircuitRelayServer 和 gossipsub 信息。并且订阅了变量为 PUBSUB_PEER_DISCOVERY 的主题。最后打印了 Relay 节点的 PeerID 信息和 Multiaddrs 信息。运行 Relay 节点后，控制台打印信息如下

Copy
> 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)
]

每一个 libp2p 节点都有一个独一无二的PeerID，该 ID 是在 Services 配置中调用 identity（）新建的身份自动生成的，如果不指定特定的 identity，那么每次都会生成不同的 PeerID。

控制台还打印了 8 个地址，这个是该 libp2p 节点的 multiaddr，其实就是对应本机上所有的 ipv4 地址，在 9001 端口上使用 WebSocket，在 9002 端口上使用 TCP。

启动浏览器节点

至此就已经成功的启动了 libp2p Relay 节点。接下来就可以启动浏览器节点，并尝试连接到 libp2p Relay 节点。新建终端并运行以下指令，并打开浏览器即可创建新的浏览器节点。

Copy
npm run start

但如果此时打开浏览器，会发现节点的 PeerID 为 Unknown，打开浏览器控制台会发现以下错误

Copy
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

根据错误信息，可以判断是 @libp2p/webrtc 需要 @libp2p/circuit-relay-v2-transport 的支持，根据上篇理论的文章，libp2p 想要实现 WebRTC 的 P2P 连接需要借助 Circuit Relay V2，二者密不可分。打开 src/index.js 文件，里面配置了浏览器的 libp2p 节点信息，在 createLibp2p 方法的 transports 配置中，为 libp2p 节点添加上 @libp2p/circuit-relay-v2-transport 支持。

Copy
transports: [
webSockets({
// Allow all WebSocket connections inclusing 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
// 添加@libp2p/circuit-relay-v2-transport支持
circuitRelayTransport({
discoverRelays: 1,
}),
]

此时打开浏览器，可以发现浏览器已经可以成功创建 libp2p 节点，每次刷新都会产生不同的 PeerID。
根据理论文章，浏览器想要加入到 libp2p 网络，需要借助 Relay 节点，那么现在浏览器要做的第一步，就是先连接 Relay 节点，在打开的浏览器页面中，输入任意一个 Relay 节点的 multiaddr 地址（注意，需要是支持 WebSocket 的地址，根据理论文章，浏览器和 Relay 节点是通过 WebSocket 建立连接的），点击 connect，成功连接后即可看到连接信息。

观察浏览器中的信息，可以发现该浏览器已经成功用 WebSockets 连接了一个节点，并且给出了该浏览器的 multiaddr。观察该 multiaddr，可以发现由两部分构成，前半部分为 Relay 节点的 multiaddr，后半部分为 /p2p-circuit/p2p / 本节点 PeerID。
在完成一个浏览器连接到 Relay 服务器后，可以尝试使用一个新的浏览器节点与之连接，构成一个 2 个浏览器，一个 relay 节点组成的 libp2p 网络。
新建一个浏览器，此时会产生一个与之前浏览器不同的 PeerID，在输入框中输入原浏览器的 multiaddr，点击 connect，此时会发现新浏览器不仅能够直接链接旧浏览器，并且也成功连接上了 Relay 节点。并显示 Circuit Relay 连接个数为 1。如果此时关闭新的浏览器，那么旧浏览器也会很快的显示与新浏览器断开连接了。

至此为止已经成功实现了浏览器的连接，但是每次打开浏览器都需要手动输入地址连接到 Relay 节点，为解决这个问题，可以在 src/index.js 的 createLibp2p 方法的 peerDiscovery 配置 bootstrap，配置为 Relay 节点地址。

Copy
peerDiscovery: [
bootstrap({
list: ['Relay节点的WebSockets地址'],
}),

然后打开新的浏览器，就会发现浏览器节点会自动与 Relay 节点链接。

启动 WebRTC 连接

在理论文章中的连接过程图中说明了浏览器节点会先通过 Circuit Relay V2 发现浏览器节点，然后再开始建立标准的 WebRTC 连接。以上步骤我们已经成功使用了通过 Circuit Relay V2 连接浏览器，接下来启动 WebRTC 连接支持。
在 src/index.js 中，为 createLibp2p 方法的 addresses 添加 listen 地址

Copy
addresses: {
listen: [
// 👇 Listen for webRTC connection
'/webrtc',
],
}

完成添加后，打开新的浏览器并连接到 Relay 节点后，会发现该浏览器的 multiaddr 数量变成了原来的两倍，观察多出的 multiaddr，可以发现新地址比原地址多添加了一个 /webrtc，这些地址在原先的基础上添加了对 WebRTC 的支持。
同样打开新的浏览器，尝试支持 WebSockets 和 WebRTC 的地址去连接旧浏览器，完成连接后可以发现 WebRTC 的连接数变为 1，至此两个浏览器之间已经成功建立 WebRTC 连接。

配置 PubSub peer discovery

在理论文章中，说明了 libp2p Relay 节点有两个作用，一个是 Circuit Relay V2，解决节点之间连接可行性问题。一个是 PubSub Peer Discovery，解决节点之间自动连接问题。
在该实践中，使用 GossipSub，通过订阅相同主题、并向以连接节点广播的方式，实现节点之间自动连接问题。
在 src/index.js 文件中，为节点配置上 pubsubPeerDiscovery 和 gossipsub。

Copy
peerDiscovery: [
bootstrap({
list: ['/ip4/127.0.0.1/tcp/9001/ws/p2p/12D3KooWKsQgy75zYnHWqoMw4fgE9R7FmjzFDD8grnsjABuXtAoN'],
}),
//新增配置，订阅主题
pubsubPeerDiscovery({
interval: 10_000,
topics: [PUBSUB_PEER_DISCOVERY],
}),
],
services: {
//新增配置，添加Gossipsub服务
pubsub: gossipsub(),
identify: identify(),
},

最后新建多个浏览器，无需进行任何操作，等待片刻，浏览器就会自动相互进行连接。至此，一个基于 JS 通过 WebRTC 在浏览器之间建立基本可用的 libp2p 网络成功建立。