使用 JS 通过 WebRTC 创建 libp2p 网络并相互连接(理论)
在上一篇理论的文章中介绍了使用 JS 通过 WebRTC 创建 libp2p 网络并相互连接的原理,在这篇博客中会逐步介绍理论中的各种特性如何具体实践。
准备项目#
实践使用的是官方提供的 libp2p-webrtc-guide 项目,这个项目提供了一个 relay 节点项目,以及一个可以在浏览器中进行交互的 libp2p 网页项目,首先 git clone 这个项目并安装依赖
#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 节点。
#启动libp2p relay节点
npm run start:relay
以上指令实质上是以 node 环境运行了 src/relay.js 文件,观察 relay.js 文件
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 节点后,控制台打印信息如下
> 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 节点。新建终端并运行以下指令,并打开浏览器即可创建新的浏览器节点。
npm run start
但如果此时打开浏览器,会发现节点的 PeerID 为 Unknown,打开浏览器控制台会发现以下错误
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 支持。
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 节点地址。
peerDiscovery: [
bootstrap({
list: ['Relay节点的WebSockets地址'],
}),
然后打开新的浏览器,就会发现浏览器节点会自动与 Relay 节点链接。
启动 WebRTC 连接#
在理论文章中的连接过程图中说明了浏览器节点会先通过 Circuit Relay V2 发现浏览器节点,然后再开始建立标准的 WebRTC 连接。以上步骤我们已经成功使用了通过 Circuit Relay V2 连接浏览器,接下来启动 WebRTC 连接支持。
在 src/index.js 中,为 createLibp2p 方法的 addresses 添加 listen 地址
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。
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 网络成功建立。