使用 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: {
// 允許本地測試的私有地址
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
根據錯誤信息,可以判斷是 @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({
// 允許所有WebSocket連接包括不帶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 網絡成功建立。