From 67af89832f0a9645b95f25f8fe989a62ea4f569e Mon Sep 17 00:00:00 2001 From: paschal533 Date: Wed, 11 Mar 2026 16:19:41 +0100 Subject: [PATCH 1/2] fix: append peer id to circuit relay addresses in peer:discovery event --- packages/libp2p/src/libp2p.ts | 21 +- .../peer-discovery/peer-discovery.spec.ts | 201 +++++++++++++++++- .../test/utils/dedupe-addresses.spec.ts | 50 +++++ 3 files changed, 268 insertions(+), 4 deletions(-) diff --git a/packages/libp2p/src/libp2p.ts b/packages/libp2p/src/libp2p.ts index 1530e965ca..b68c59dd48 100644 --- a/packages/libp2p/src/libp2p.ts +++ b/packages/libp2p/src/libp2p.ts @@ -4,7 +4,7 @@ import { defaultLogger } from '@libp2p/logger' import { PeerSet } from '@libp2p/peer-collections' import { peerIdFromString } from '@libp2p/peer-id' import { persistentPeerStore } from '@libp2p/peer-store' -import { CODE_P2P, isMultiaddr } from '@multiformats/multiaddr' +import { CODE_P2P, CODE_P2P_CIRCUIT, isMultiaddr, multiaddr } from '@multiformats/multiaddr' import { MemoryDatastore } from 'datastore-core/memory' import { TypedEventEmitter, setMaxListeners } from 'main-event' import { concat as uint8ArrayConcat } from 'uint8arrays/concat' @@ -100,9 +100,24 @@ export class Libp2p extends TypedEventEmitter components.events.addEventListener('peer:update', evt => { // if there was no peer previously in the peer store this is a new peer if (evt.detail.previous == null) { + const id = evt.detail.peer.id const peerInfo: PeerInfo = { - id: evt.detail.peer.id, - multiaddrs: evt.detail.peer.addresses.map(a => a.multiaddr) + id, + multiaddrs: evt.detail.peer.addresses.map(a => { + const ma = a.multiaddr + const components = ma.getComponents() + const circuitIdx = components.findIndex(c => c.code === CODE_P2P_CIRCUIT) + + // For circuit relay addresses, ensure the target peer ID is appended + // so callers can dial the address directly without having to add it manually. + // Peers often announce relay addresses without their own peer ID (e.g. from + // identify), so we add it here since it is known from the peer store. + if (circuitIdx !== -1 && !components.slice(circuitIdx + 1).some(c => c.code === CODE_P2P)) { + return ma.encapsulate(multiaddr(`/p2p/${id}`)) + } + + return ma + }) } components.events.safeDispatchEvent('peer:discovery', { detail: peerInfo }) diff --git a/packages/libp2p/test/peer-discovery/peer-discovery.spec.ts b/packages/libp2p/test/peer-discovery/peer-discovery.spec.ts index 2ed69b559e..800b9bd8d7 100644 --- a/packages/libp2p/test/peer-discovery/peer-discovery.spec.ts +++ b/packages/libp2p/test/peer-discovery/peer-discovery.spec.ts @@ -1,10 +1,13 @@ +import { generateKeyPair } from '@libp2p/crypto/keys' +import { peerIdFromPrivateKey } from '@libp2p/peer-id' import { multiaddr } from '@multiformats/multiaddr' import { expect } from 'aegir/chai' import { TypedEventEmitter } from 'main-event' +import { pEvent } from 'p-event' import sinon from 'sinon' import { stubInterface } from 'sinon-ts' import { createLibp2p } from '../../src/index.js' -import type { PeerDiscovery, PeerDiscoveryEvents, Startable, Libp2p } from '@libp2p/interface' +import type { PeerDiscovery, PeerDiscoveryEvents, PeerInfo, Startable, Libp2p } from '@libp2p/interface' describe('peer discovery', () => { let libp2p: Libp2p @@ -33,6 +36,202 @@ describe('peer discovery', () => { expect(discovery.stop.calledOnce).to.be.true() }) + it('should append peer id to circuit relay addresses in peer:discovery event', async () => { + const discovery = new TypedEventEmitter() + + libp2p = await createLibp2p({ + peerDiscovery: [() => discovery] + }) + + await libp2p.start() + + const remotePeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + const relayPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + + // Address without target peer ID (e.g. as announced via identify by the remote peer) + const relayAddr = multiaddr(`/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit`) + + const eventPromise = pEvent<'peer:discovery', CustomEvent>(libp2p, 'peer:discovery') + + discovery.safeDispatchEvent('peer', { + detail: { + id: remotePeerId, + multiaddrs: [relayAddr] + } + }) + + const evt = await eventPromise + + expect(evt.detail.id.toString()).to.equal(remotePeerId.toString()) + expect(evt.detail.multiaddrs).to.have.length(1) + expect(evt.detail.multiaddrs[0].toString()).to.equal( + `/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/p2p/${remotePeerId}` + ) + }) + + it('should not duplicate peer id in circuit relay addresses that already have one', async () => { + const discovery = new TypedEventEmitter() + + libp2p = await createLibp2p({ + peerDiscovery: [() => discovery] + }) + + await libp2p.start() + + const remotePeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + const relayPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + + // Address already has the target peer ID (e.g. as sent by pubsub-peer-discovery) + const relayAddrWithPeerId = multiaddr(`/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/p2p/${remotePeerId}`) + + const eventPromise = pEvent<'peer:discovery', CustomEvent>(libp2p, 'peer:discovery') + + discovery.safeDispatchEvent('peer', { + detail: { + id: remotePeerId, + multiaddrs: [relayAddrWithPeerId] + } + }) + + const evt = await eventPromise + + expect(evt.detail.id.toString()).to.equal(remotePeerId.toString()) + expect(evt.detail.multiaddrs).to.have.length(1) + expect(evt.detail.multiaddrs[0].toString()).to.equal( + `/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/p2p/${remotePeerId}` + ) + }) + + it('should not modify direct (non-relay) addresses in peer:discovery event', async () => { + const discovery = new TypedEventEmitter() + + libp2p = await createLibp2p({ + peerDiscovery: [() => discovery] + }) + + await libp2p.start() + + const remotePeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + const directAddr = multiaddr('/ip4/1.2.3.4/tcp/4001') + + const eventPromise = pEvent<'peer:discovery', CustomEvent>(libp2p, 'peer:discovery') + + discovery.safeDispatchEvent('peer', { + detail: { + id: remotePeerId, + multiaddrs: [directAddr] + } + }) + + const evt = await eventPromise + + expect(evt.detail.multiaddrs).to.have.length(1) + expect(evt.detail.multiaddrs[0].toString()).to.equal('/ip4/1.2.3.4/tcp/4001') + }) + + it('should append peer id to WebRTC circuit relay addresses missing one', async () => { + const discovery = new TypedEventEmitter() + + libp2p = await createLibp2p({ + peerDiscovery: [() => discovery] + }) + + await libp2p.start() + + const remotePeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + const relayPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + + // WebRTC relay address without target peer ID + const webrtcRelayAddr = multiaddr(`/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/webrtc`) + + const eventPromise = pEvent<'peer:discovery', CustomEvent>(libp2p, 'peer:discovery') + + discovery.safeDispatchEvent('peer', { + detail: { + id: remotePeerId, + multiaddrs: [webrtcRelayAddr] + } + }) + + const evt = await eventPromise + + expect(evt.detail.multiaddrs).to.have.length(1) + expect(evt.detail.multiaddrs[0].toString()).to.equal( + `/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/webrtc/p2p/${remotePeerId}` + ) + }) + + it('should not duplicate peer id in WebRTC circuit relay addresses that already have one', async () => { + const discovery = new TypedEventEmitter() + + libp2p = await createLibp2p({ + peerDiscovery: [() => discovery] + }) + + await libp2p.start() + + const remotePeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + const relayPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + + // WebRTC relay address that already includes the target peer ID (e.g. from pubsub-peer-discovery) + const webrtcRelayAddrWithPeerId = multiaddr(`/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/webrtc/p2p/${remotePeerId}`) + + const eventPromise = pEvent<'peer:discovery', CustomEvent>(libp2p, 'peer:discovery') + + discovery.safeDispatchEvent('peer', { + detail: { + id: remotePeerId, + multiaddrs: [webrtcRelayAddrWithPeerId] + } + }) + + const evt = await eventPromise + + expect(evt.detail.multiaddrs).to.have.length(1) + expect(evt.detail.multiaddrs[0].toString()).to.equal( + `/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/webrtc/p2p/${remotePeerId}` + ) + }) + + it('should handle mixed relay and direct addresses correctly in peer:discovery event', async () => { + const discovery = new TypedEventEmitter() + + libp2p = await createLibp2p({ + peerDiscovery: [() => discovery] + }) + + await libp2p.start() + + const remotePeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + const relayPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + + const directAddr = multiaddr('/ip4/1.2.3.4/tcp/4001') + const relayAddrNoId = multiaddr(`/ip4/5.6.7.8/tcp/1234/p2p/${relayPeerId}/p2p-circuit`) + const relayAddrWithId = multiaddr(`/ip4/9.10.11.12/tcp/5678/p2p/${relayPeerId}/p2p-circuit/p2p/${remotePeerId}`) + + const eventPromise = pEvent<'peer:discovery', CustomEvent>(libp2p, 'peer:discovery') + + discovery.safeDispatchEvent('peer', { + detail: { + id: remotePeerId, + multiaddrs: [directAddr, relayAddrNoId, relayAddrWithId] + } + }) + + const evt = await eventPromise + + const addrStrings = evt.detail.multiaddrs.map(ma => ma.toString()) + + // Direct address unchanged + expect(addrStrings).to.include('/ip4/1.2.3.4/tcp/4001') + // Relay without peer ID gets it appended + expect(addrStrings).to.include(`/ip4/5.6.7.8/tcp/1234/p2p/${relayPeerId}/p2p-circuit/p2p/${remotePeerId}`) + // Relay that already has peer ID is not modified + expect(addrStrings).to.include(`/ip4/9.10.11.12/tcp/5678/p2p/${relayPeerId}/p2p-circuit/p2p/${remotePeerId}`) + // No address should have a double peer ID + expect(addrStrings.every(a => !a.includes(`/p2p/${remotePeerId}/p2p/${remotePeerId}`))).to.be.true() + }) + it('should ignore self on discovery', async () => { const discovery = new TypedEventEmitter() diff --git a/packages/peer-store/test/utils/dedupe-addresses.spec.ts b/packages/peer-store/test/utils/dedupe-addresses.spec.ts index 9191739440..3090ab4907 100644 --- a/packages/peer-store/test/utils/dedupe-addresses.spec.ts +++ b/packages/peer-store/test/utils/dedupe-addresses.spec.ts @@ -66,6 +66,56 @@ describe('dedupe-addresses', () => { }]) }) + it('should preserve target peer id in circuit relay addresses', async () => { + const relayPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + const targetPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + + // Address that includes the target peer ID after /p2p-circuit (e.g. from pubsub-peer-discovery) + const relayAddr = multiaddr(`/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/p2p/${targetPeerId}`) + + const result = await dedupeFilterAndSortAddresses(targetPeerId, async () => true, [{ + multiaddr: relayAddr, + isCertified: false + }]) + + expect(result).to.have.length(1) + // The trailing /p2p/TARGET_ID must not be stripped - it is needed for dialling via relay + expect(multiaddr(result[0].multiaddr).toString()).to.equal(relayAddr.toString()) + }) + + it('should preserve target peer id in WebRTC circuit relay addresses', async () => { + const relayPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + const targetPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + + // WebRTC browser-to-browser relay address includes /webrtc before the target peer ID + const webrtcRelayAddr = multiaddr(`/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/webrtc/p2p/${targetPeerId}`) + + const result = await dedupeFilterAndSortAddresses(targetPeerId, async () => true, [{ + multiaddr: webrtcRelayAddr, + isCertified: false + }]) + + expect(result).to.have.length(1) + expect(multiaddr(result[0].multiaddr).toString()).to.equal(webrtcRelayAddr.toString()) + }) + + it('should strip peer id from direct addresses', async () => { + const targetPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + + // Direct address with redundant peer ID appended (common from identify / pubsub-peer-discovery) + const directAddrWithPeerId = multiaddr(`/ip4/1.2.3.4/tcp/4001/p2p/${targetPeerId}`) + const directAddr = multiaddr('/ip4/1.2.3.4/tcp/4001') + + const result = await dedupeFilterAndSortAddresses(targetPeerId, async () => true, [{ + multiaddr: directAddrWithPeerId, + isCertified: false + }]) + + expect(result).to.have.length(1) + // Peer ID is stripped from direct addresses in storage (it is redundant - known from peer store key) + expect(multiaddr(result[0].multiaddr).toString()).to.equal(directAddr.toString()) + }) + it('should filter addresses', async () => { expect(await dedupeFilterAndSortAddresses(peerId, async () => false, [{ multiaddr: addr1, From 082308cf4adf0b609a13b2e9a8269d72d8c50180 Mon Sep 17 00:00:00 2001 From: paschal533 Date: Tue, 17 Mar 2026 11:08:43 +0100 Subject: [PATCH 2/2] fix(connection-manager): dial by peer id to correctly resolve relay addresses --- packages/libp2p/src/libp2p.ts | 21 +- .../connection-manager/dial-queue.spec.ts | 63 ++++++ .../peer-discovery/peer-discovery.spec.ts | 201 +----------------- 3 files changed, 67 insertions(+), 218 deletions(-) diff --git a/packages/libp2p/src/libp2p.ts b/packages/libp2p/src/libp2p.ts index b68c59dd48..1530e965ca 100644 --- a/packages/libp2p/src/libp2p.ts +++ b/packages/libp2p/src/libp2p.ts @@ -4,7 +4,7 @@ import { defaultLogger } from '@libp2p/logger' import { PeerSet } from '@libp2p/peer-collections' import { peerIdFromString } from '@libp2p/peer-id' import { persistentPeerStore } from '@libp2p/peer-store' -import { CODE_P2P, CODE_P2P_CIRCUIT, isMultiaddr, multiaddr } from '@multiformats/multiaddr' +import { CODE_P2P, isMultiaddr } from '@multiformats/multiaddr' import { MemoryDatastore } from 'datastore-core/memory' import { TypedEventEmitter, setMaxListeners } from 'main-event' import { concat as uint8ArrayConcat } from 'uint8arrays/concat' @@ -100,24 +100,9 @@ export class Libp2p extends TypedEventEmitter components.events.addEventListener('peer:update', evt => { // if there was no peer previously in the peer store this is a new peer if (evt.detail.previous == null) { - const id = evt.detail.peer.id const peerInfo: PeerInfo = { - id, - multiaddrs: evt.detail.peer.addresses.map(a => { - const ma = a.multiaddr - const components = ma.getComponents() - const circuitIdx = components.findIndex(c => c.code === CODE_P2P_CIRCUIT) - - // For circuit relay addresses, ensure the target peer ID is appended - // so callers can dial the address directly without having to add it manually. - // Peers often announce relay addresses without their own peer ID (e.g. from - // identify), so we add it here since it is known from the peer store. - if (circuitIdx !== -1 && !components.slice(circuitIdx + 1).some(c => c.code === CODE_P2P)) { - return ma.encapsulate(multiaddr(`/p2p/${id}`)) - } - - return ma - }) + id: evt.detail.peer.id, + multiaddrs: evt.detail.peer.addresses.map(a => a.multiaddr) } components.events.safeDispatchEvent('peer:discovery', { detail: peerInfo }) diff --git a/packages/libp2p/test/connection-manager/dial-queue.spec.ts b/packages/libp2p/test/connection-manager/dial-queue.spec.ts index 09c4d92ca8..0395ce89d8 100644 --- a/packages/libp2p/test/connection-manager/dial-queue.spec.ts +++ b/packages/libp2p/test/connection-manager/dial-queue.spec.ts @@ -472,4 +472,67 @@ describe('dial queue', () => { await expect(dial1).to.eventually.equal(connection) await expect(dial2).to.eventually.equal(connection) }) + + it('should append peer id to circuit relay addresses that are missing it', async () => { + const remotePeer = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + const relayPeer = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + + // relay address as stored in the peer store - no destination peer id + // (PeerInfo multiaddrs intentionally omit the destination peer id for wire efficiency) + const relayAddrWithoutPeerId = multiaddr(`/ip4/1.2.3.4/tcp/1234/p2p/${relayPeer}/p2p-circuit`) + const relayAddrWithPeerId = multiaddr(`/ip4/1.2.3.4/tcp/1234/p2p/${relayPeer}/p2p-circuit/p2p/${remotePeer}`) + const connection = stubInterface({ remotePeer }) + + components.peerStore.get.withArgs(remotePeer).resolves({ + id: remotePeer, + addresses: [{ multiaddr: relayAddrWithoutPeerId, isCertified: false }], + protocols: [], + metadata: new Map(), + tags: new Map() + }) + + components.transportManager.dialTransportForMultiaddr.returns(stubInterface()) + components.transportManager.dial.callsFake(async (ma) => { + if (ma.equals(relayAddrWithPeerId)) { + return connection + } + throw new Error(`unexpected address: ${ma.toString()}`) + }) + + dialer = new DialQueue(components) + + await expect(dialer.dial(remotePeer)).to.eventually.equal(connection) + + // the transport was called with the full relay address including the destination peer id + expect(components.transportManager.dial.calledWith(relayAddrWithPeerId)).to.be.true() + }) + + it('should not duplicate peer id in circuit relay addresses that already have it', async () => { + const remotePeer = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + const relayPeer = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + + // relay address already has the destination peer id (e.g. from pubsub-peer-discovery) + const relayAddrWithPeerId = multiaddr(`/ip4/1.2.3.4/tcp/1234/p2p/${relayPeer}/p2p-circuit/p2p/${remotePeer}`) + const connection = stubInterface({ remotePeer }) + + components.peerStore.get.withArgs(remotePeer).resolves({ + id: remotePeer, + addresses: [{ multiaddr: relayAddrWithPeerId, isCertified: false }], + protocols: [], + metadata: new Map(), + tags: new Map() + }) + + components.transportManager.dialTransportForMultiaddr.returns(stubInterface()) + components.transportManager.dial.resolves(connection) + + dialer = new DialQueue(components) + + await expect(dialer.dial(remotePeer)).to.eventually.equal(connection) + + // the address passed to the transport must not have a double peer id + const dialledAddr = components.transportManager.dial.getCall(0).args[0].toString() + expect(dialledAddr).to.equal(relayAddrWithPeerId.toString()) + expect(dialledAddr).to.not.include(`/p2p/${remotePeer}/p2p/${remotePeer}`) + }) }) diff --git a/packages/libp2p/test/peer-discovery/peer-discovery.spec.ts b/packages/libp2p/test/peer-discovery/peer-discovery.spec.ts index 800b9bd8d7..2ed69b559e 100644 --- a/packages/libp2p/test/peer-discovery/peer-discovery.spec.ts +++ b/packages/libp2p/test/peer-discovery/peer-discovery.spec.ts @@ -1,13 +1,10 @@ -import { generateKeyPair } from '@libp2p/crypto/keys' -import { peerIdFromPrivateKey } from '@libp2p/peer-id' import { multiaddr } from '@multiformats/multiaddr' import { expect } from 'aegir/chai' import { TypedEventEmitter } from 'main-event' -import { pEvent } from 'p-event' import sinon from 'sinon' import { stubInterface } from 'sinon-ts' import { createLibp2p } from '../../src/index.js' -import type { PeerDiscovery, PeerDiscoveryEvents, PeerInfo, Startable, Libp2p } from '@libp2p/interface' +import type { PeerDiscovery, PeerDiscoveryEvents, Startable, Libp2p } from '@libp2p/interface' describe('peer discovery', () => { let libp2p: Libp2p @@ -36,202 +33,6 @@ describe('peer discovery', () => { expect(discovery.stop.calledOnce).to.be.true() }) - it('should append peer id to circuit relay addresses in peer:discovery event', async () => { - const discovery = new TypedEventEmitter() - - libp2p = await createLibp2p({ - peerDiscovery: [() => discovery] - }) - - await libp2p.start() - - const remotePeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) - const relayPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) - - // Address without target peer ID (e.g. as announced via identify by the remote peer) - const relayAddr = multiaddr(`/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit`) - - const eventPromise = pEvent<'peer:discovery', CustomEvent>(libp2p, 'peer:discovery') - - discovery.safeDispatchEvent('peer', { - detail: { - id: remotePeerId, - multiaddrs: [relayAddr] - } - }) - - const evt = await eventPromise - - expect(evt.detail.id.toString()).to.equal(remotePeerId.toString()) - expect(evt.detail.multiaddrs).to.have.length(1) - expect(evt.detail.multiaddrs[0].toString()).to.equal( - `/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/p2p/${remotePeerId}` - ) - }) - - it('should not duplicate peer id in circuit relay addresses that already have one', async () => { - const discovery = new TypedEventEmitter() - - libp2p = await createLibp2p({ - peerDiscovery: [() => discovery] - }) - - await libp2p.start() - - const remotePeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) - const relayPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) - - // Address already has the target peer ID (e.g. as sent by pubsub-peer-discovery) - const relayAddrWithPeerId = multiaddr(`/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/p2p/${remotePeerId}`) - - const eventPromise = pEvent<'peer:discovery', CustomEvent>(libp2p, 'peer:discovery') - - discovery.safeDispatchEvent('peer', { - detail: { - id: remotePeerId, - multiaddrs: [relayAddrWithPeerId] - } - }) - - const evt = await eventPromise - - expect(evt.detail.id.toString()).to.equal(remotePeerId.toString()) - expect(evt.detail.multiaddrs).to.have.length(1) - expect(evt.detail.multiaddrs[0].toString()).to.equal( - `/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/p2p/${remotePeerId}` - ) - }) - - it('should not modify direct (non-relay) addresses in peer:discovery event', async () => { - const discovery = new TypedEventEmitter() - - libp2p = await createLibp2p({ - peerDiscovery: [() => discovery] - }) - - await libp2p.start() - - const remotePeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) - const directAddr = multiaddr('/ip4/1.2.3.4/tcp/4001') - - const eventPromise = pEvent<'peer:discovery', CustomEvent>(libp2p, 'peer:discovery') - - discovery.safeDispatchEvent('peer', { - detail: { - id: remotePeerId, - multiaddrs: [directAddr] - } - }) - - const evt = await eventPromise - - expect(evt.detail.multiaddrs).to.have.length(1) - expect(evt.detail.multiaddrs[0].toString()).to.equal('/ip4/1.2.3.4/tcp/4001') - }) - - it('should append peer id to WebRTC circuit relay addresses missing one', async () => { - const discovery = new TypedEventEmitter() - - libp2p = await createLibp2p({ - peerDiscovery: [() => discovery] - }) - - await libp2p.start() - - const remotePeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) - const relayPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) - - // WebRTC relay address without target peer ID - const webrtcRelayAddr = multiaddr(`/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/webrtc`) - - const eventPromise = pEvent<'peer:discovery', CustomEvent>(libp2p, 'peer:discovery') - - discovery.safeDispatchEvent('peer', { - detail: { - id: remotePeerId, - multiaddrs: [webrtcRelayAddr] - } - }) - - const evt = await eventPromise - - expect(evt.detail.multiaddrs).to.have.length(1) - expect(evt.detail.multiaddrs[0].toString()).to.equal( - `/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/webrtc/p2p/${remotePeerId}` - ) - }) - - it('should not duplicate peer id in WebRTC circuit relay addresses that already have one', async () => { - const discovery = new TypedEventEmitter() - - libp2p = await createLibp2p({ - peerDiscovery: [() => discovery] - }) - - await libp2p.start() - - const remotePeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) - const relayPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) - - // WebRTC relay address that already includes the target peer ID (e.g. from pubsub-peer-discovery) - const webrtcRelayAddrWithPeerId = multiaddr(`/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/webrtc/p2p/${remotePeerId}`) - - const eventPromise = pEvent<'peer:discovery', CustomEvent>(libp2p, 'peer:discovery') - - discovery.safeDispatchEvent('peer', { - detail: { - id: remotePeerId, - multiaddrs: [webrtcRelayAddrWithPeerId] - } - }) - - const evt = await eventPromise - - expect(evt.detail.multiaddrs).to.have.length(1) - expect(evt.detail.multiaddrs[0].toString()).to.equal( - `/ip4/1.2.3.4/tcp/1234/p2p/${relayPeerId}/p2p-circuit/webrtc/p2p/${remotePeerId}` - ) - }) - - it('should handle mixed relay and direct addresses correctly in peer:discovery event', async () => { - const discovery = new TypedEventEmitter() - - libp2p = await createLibp2p({ - peerDiscovery: [() => discovery] - }) - - await libp2p.start() - - const remotePeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) - const relayPeerId = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) - - const directAddr = multiaddr('/ip4/1.2.3.4/tcp/4001') - const relayAddrNoId = multiaddr(`/ip4/5.6.7.8/tcp/1234/p2p/${relayPeerId}/p2p-circuit`) - const relayAddrWithId = multiaddr(`/ip4/9.10.11.12/tcp/5678/p2p/${relayPeerId}/p2p-circuit/p2p/${remotePeerId}`) - - const eventPromise = pEvent<'peer:discovery', CustomEvent>(libp2p, 'peer:discovery') - - discovery.safeDispatchEvent('peer', { - detail: { - id: remotePeerId, - multiaddrs: [directAddr, relayAddrNoId, relayAddrWithId] - } - }) - - const evt = await eventPromise - - const addrStrings = evt.detail.multiaddrs.map(ma => ma.toString()) - - // Direct address unchanged - expect(addrStrings).to.include('/ip4/1.2.3.4/tcp/4001') - // Relay without peer ID gets it appended - expect(addrStrings).to.include(`/ip4/5.6.7.8/tcp/1234/p2p/${relayPeerId}/p2p-circuit/p2p/${remotePeerId}`) - // Relay that already has peer ID is not modified - expect(addrStrings).to.include(`/ip4/9.10.11.12/tcp/5678/p2p/${relayPeerId}/p2p-circuit/p2p/${remotePeerId}`) - // No address should have a double peer ID - expect(addrStrings.every(a => !a.includes(`/p2p/${remotePeerId}/p2p/${remotePeerId}`))).to.be.true() - }) - it('should ignore self on discovery', async () => { const discovery = new TypedEventEmitter()