Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions js-peer/src/context/ctx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import { ChatProvider } from './chat-ctx'
import type { Libp2p, PubSub } from '@libp2p/interface'
import type { Identify } from '@libp2p/identify'
import type { DirectMessage } from '@/lib/direct-message'
import type { DelegatedRoutingV1HttpApiClient } from '@helia/delegated-routing-v1-http-api-client'
import { Booting } from '@/components/booting'

export type Libp2pType = Libp2p<{
pubsub: PubSub
identify: Identify
directMessage: DirectMessage
delegatedRouting: DelegatedRoutingV1HttpApiClient
}>

export const libp2pContext = createContext<{ libp2p: Libp2pType }>({
Expand Down
13 changes: 8 additions & 5 deletions js-peer/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ export const CIRCUIT_RELAY_CODE = 290

export const MIME_TEXT_PLAIN = 'text/plain'

// 👇 App specific dedicated bootstrap PeerIDs
// Their multiaddrs are ephemeral so peer routing is used to resolve multiaddr
export const WEBTRANSPORT_BOOTSTRAP_PEER_ID = '12D3KooWFhXabKDwALpzqMbto94sB7rvmZ6M28hs9Y9xSopDKwQr'

export const BOOTSTRAP_PEER_IDS = [WEBTRANSPORT_BOOTSTRAP_PEER_ID]
export const BOOTSTRAP_MULTIADDRS = [
'/dns4/burden-sphere-chuckle-fever.2n6.me/tcp/443/tls/ws/p2p/16Uiu2HAm2BG1xBWz8YyqFiERC7vJzUX2mpyF1vNyVMJajQcebVeB',
'/dns6/burden-sphere-chuckle-fever.2n6.me/tcp/443/tls/ws/p2p/16Uiu2HAm2BG1xBWz8YyqFiERC7vJzUX2mpyF1vNyVMJajQcebVeB',
'/ip4/37.114.50.44/udp/24204/quic-v1/webtransport/p2p/16Uiu2HAm2BG1xBWz8YyqFiERC7vJzUX2mpyF1vNyVMJajQcebVeB',
'/ip6/2a0e:97c0:3e3:54b:3:1e9a:2ca2:2ab1/udp/24204/quic-v1/webtransport/p2p/16Uiu2HAm2BG1xBWz8YyqFiERC7vJzUX2mpyF1vNyVMJajQcebVeB',
'/ip4/37.114.50.44/udp/24204/webrtc-direct/p2p/16Uiu2HAm2BG1xBWz8YyqFiERC7vJzUX2mpyF1vNyVMJajQcebVeB',
'/ip6/2a0e:97c0:3e3:54b:3:1e9a:2ca2:2ab1/udp/24204/webrtc-direct/p2p/16Uiu2HAm2BG1xBWz8YyqFiERC7vJzUX2mpyF1vNyVMJajQcebVeB',
] as const
76 changes: 16 additions & 60 deletions js-peer/src/lib/libp2p.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import {
createDelegatedRoutingV1HttpApiClient,
DelegatedRoutingV1HttpApiClient,
} from '@helia/delegated-routing-v1-http-api-client'
import { createLibp2p } from 'libp2p'
import { bootstrap } from '@libp2p/bootstrap'
import { identify } from '@libp2p/identify'
import { peerIdFromString } from '@libp2p/peer-id'
import { noise } from '@chainsafe/libp2p-noise'
import { yamux } from '@chainsafe/libp2p-yamux'
import { Multiaddr } from '@multiformats/multiaddr'
import { sha256 } from 'multiformats/hashes/sha2'
import type { Connection, Message, SignedMessage, PeerId, Libp2p } from '@libp2p/interface'
import type { Connection, Message, SignedMessage, Libp2p } from '@libp2p/interface'
import { gossipsub } from '@chainsafe/libp2p-gossipsub'
import { webSockets } from '@libp2p/websockets'
import { webTransport } from '@libp2p/webtransport'
import { webRTC, webRTCDirect } from '@libp2p/webrtc'
import { circuitRelayTransport } from '@libp2p/circuit-relay-v2'
import { pubsubPeerDiscovery } from '@libp2p/pubsub-peer-discovery'
import { ping } from '@libp2p/ping'
import { BOOTSTRAP_PEER_IDS, CHAT_FILE_TOPIC, CHAT_TOPIC, PUBSUB_PEER_DISCOVERY } from './constants'
import first from 'it-first'
import { BOOTSTRAP_MULTIADDRS, CHAT_FILE_TOPIC, CHAT_TOPIC, PUBSUB_PEER_DISCOVERY } from './constants'
import { forComponent, enable } from './logger'
import { directMessage } from './direct-message'
import type { Libp2pType } from '@/context/ctx'
Expand All @@ -29,10 +24,7 @@ export async function startLibp2p(): Promise<Libp2pType> {
// enable verbose logging in browser console to view debug logs
enable('ui*,libp2p*,-libp2p:connection-manager*,-*:trace')

const delegatedClient = createDelegatedRoutingV1HttpApiClient('https://delegated-ipfs.dev')

const relayListenAddrs = await getRelayListenAddrs(delegatedClient)
log('starting libp2p with relayListenAddrs: %o', relayListenAddrs)
log('starting libp2p with bootstrap multiaddrs: %o', BOOTSTRAP_MULTIADDRS)

let libp2p: Libp2pType

Expand All @@ -41,7 +33,6 @@ export async function startLibp2p(): Promise<Libp2pType> {
listen: [
// 👇 Listen for webRTC connection
'/webrtc',
...relayListenAddrs,
],
},
transports: [
Expand All @@ -59,6 +50,11 @@ export async function startLibp2p(): Promise<Libp2pType> {
denyDialMultiaddr: async () => false,
},
peerDiscovery: [
bootstrap({
list: [...BOOTSTRAP_MULTIADDRS],
timeout: 10_000,
tagName: 'uc-bootstrap',
}),
pubsubPeerDiscovery({
interval: 10_000,
topics: [PUBSUB_PEER_DISCOVERY],
Expand All @@ -71,9 +67,6 @@ export async function startLibp2p(): Promise<Libp2pType> {
msgIdFn: msgIdFnStrictNoSign,
ignoreDuplicatePublishError: true,
}),
// Delegated routing helps us discover the ephemeral multiaddrs of the dedicated go and rust bootstrap peers
// This relies on the public delegated routing endpoint https://docs.ipfs.tech/concepts/public-utilities/#delegated-routing
delegatedRouting: () => delegatedClient,
identify: identify(),
// Custom protocol for direct messaging
directMessage: directMessage(),
Expand Down Expand Up @@ -102,7 +95,13 @@ export async function startLibp2p(): Promise<Libp2pType> {
return
}

dialWebRTCMaddrs(libp2p, multiaddrs)
// libp2p.dial() already deduplicates existing and in-flight connection attempts
// for the same peer and can try multiple candidate multiaddrs until one succeeds.
log(`dialling discovered multiaddrs: %o`, multiaddrs)

void libp2p.dial(multiaddrs).catch((error) => {
log.error(`failed to dial discovered multiaddrs for peer %s: %o`, id, error)
})
})

return libp2p
Expand All @@ -119,23 +118,6 @@ export async function msgIdFnStrictNoSign(msg: Message): Promise<Uint8Array> {
return await sha256.encode(encodedSeqNum)
}

// Function which dials one maddr at a time to avoid establishing multiple connections to the same peer
async function dialWebRTCMaddrs(libp2p: Libp2p, multiaddrs: Multiaddr[]): Promise<void> {
// Filter webrtc (browser-to-browser) multiaddrs
const webRTCMadrs = multiaddrs.filter((maddr) => maddr.protoNames().includes('webrtc'))
log(`dialling WebRTC multiaddrs: %o`, webRTCMadrs)

for (const addr of webRTCMadrs) {
try {
log(`attempting to dial webrtc multiaddr: %o`, addr)
await libp2p.dial(addr)
return // if we succeed dialing the peer, no need to try another address
} catch (error) {
log.error(`failed to dial webrtc multiaddr: %o`, addr)
}
}
}

export const connectToMultiaddr = (libp2p: Libp2p) => async (multiaddr: Multiaddr) => {
log(`dialling: %a`, multiaddr)
try {
Expand All @@ -148,32 +130,6 @@ export const connectToMultiaddr = (libp2p: Libp2p) => async (multiaddr: Multiadd
}
}

// Function which resolves PeerIDs of rust/go bootstrap nodes to multiaddrs dialable from the browser
// Returns both the dialable multiaddrs in addition to the relay
async function getRelayListenAddrs(client: DelegatedRoutingV1HttpApiClient): Promise<string[]> {
const peers = await Promise.all(BOOTSTRAP_PEER_IDS.map((peerId) => first(client.getPeers(peerIdFromString(peerId)))))

const relayListenAddrs = []
for (const p of peers) {
if (p && p.Addrs.length > 0) {
for (const maddr of p.Addrs) {
const protos = maddr.protoNames()
// Note: narrowing to Secure WebSockets and IP4 addresses to avoid potential issues with ipv6
// https://github.com/libp2p/js-libp2p/issues/2977
if (protos.includes('tls') && protos.includes('ws')) {
if (maddr.nodeAddress().address === '127.0.0.1') continue // skip loopback
relayListenAddrs.push(getRelayListenAddr(maddr, p.ID))
}
}
}
}
return relayListenAddrs
}

// Constructs a multiaddr string representing the circuit relay v2 listen address for a relayed connection to the given peer.
const getRelayListenAddr = (maddr: Multiaddr, peer: PeerId): string =>
`${maddr.toString()}/p2p/${peer.toString()}/p2p-circuit`

export const getFormattedConnections = (connections: Connection[]) =>
connections.map((conn) => ({
peerId: conn.remotePeer,
Expand Down
Loading