Skip to content

Commit 06f4901

Browse files
wemeetagainmaschad
andauthored
feat: enable manual identify (#1784)
In some applications, automatically dialing identify is not desired, and more manual control is useful. To that end, this PR contains two edits that act together - Add an additional identify init boolean option `runOnConnectionOpen`, which controls whether to listen to `'connection:open'` events to trigger identify - Add a return value to the `identify` method --------- Co-authored-by: chad <chad.nehemiah94@gmail.com>
1 parent 7b5c54d commit 06f4901

File tree

7 files changed

+112
-50
lines changed

7 files changed

+112
-50
lines changed

interop/test/ping.spec.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ import { webTransport } from '@libp2p/webtransport'
1313
import { type Multiaddr, multiaddr } from '@multiformats/multiaddr'
1414
import { createLibp2p, type Libp2p, type Libp2pOptions } from 'libp2p'
1515
import { circuitRelayTransport } from 'libp2p/circuit-relay'
16-
import { identifyService } from 'libp2p/identify'
16+
import { IdentifyService, identifyService } from 'libp2p/identify'
1717
import { pingService, type PingService } from 'libp2p/ping'
18-
import type { DefaultIdentifyService } from 'libp2p/identify/identify'
1918

2019
async function redisProxy (commands: any[]): Promise<any> {
2120
const res = await fetch(`http://localhost:${process.env.proxyPort ?? ''}/`, { body: JSON.stringify(commands), method: 'POST' })
@@ -25,7 +24,7 @@ async function redisProxy (commands: any[]): Promise<any> {
2524
return res.json()
2625
}
2726

28-
let node: Libp2p<{ ping: PingService, identify: DefaultIdentifyService }>
27+
let node: Libp2p<{ ping: PingService, identify: IdentifyService }>
2928
const isDialer: boolean = process.env.is_dialer === 'true'
3029
const timeoutSecs: string = process.env.test_timeout_secs ?? '180'
3130

@@ -38,7 +37,7 @@ describe('ping test', () => {
3837
const MUXER = process.env.muxer
3938
const IP = process.env.ip ?? '0.0.0.0'
4039

41-
const options: Libp2pOptions<{ ping: PingService, identify: DefaultIdentifyService }> = {
40+
const options: Libp2pOptions<{ ping: PingService, identify: IdentifyService }> = {
4241
start: true,
4342
connectionGater: {
4443
denyDialMultiaddr: async () => false

packages/libp2p/src/identify/identify.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION
2424
} from './consts.js'
2525
import { Identify } from './pb/message.js'
26-
import type { IdentifyServiceComponents, IdentifyServiceInit } from './index.js'
26+
import type { IdentifyService, IdentifyServiceComponents, IdentifyServiceInit } from './index.js'
2727
import type { Libp2pEvents, IdentifyResult, SignedPeerRecord, AbortOptions } from '@libp2p/interface'
2828
import type { Connection, Stream } from '@libp2p/interface/connection'
2929
import type { EventEmitter } from '@libp2p/interface/events'
@@ -49,10 +49,11 @@ const defaultValues = {
4949
maxPushIncomingStreams: 1,
5050
maxPushOutgoingStreams: 1,
5151
maxObservedAddresses: 10,
52-
maxIdentifyMessageSize: 8192
52+
maxIdentifyMessageSize: 8192,
53+
runOnConnectionOpen: true
5354
}
5455

55-
export class DefaultIdentifyService implements Startable {
56+
export class DefaultIdentifyService implements Startable, IdentifyService {
5657
private readonly identifyProtocolStr: string
5758
private readonly identifyPushProtocolStr: string
5859
public readonly host: {
@@ -100,11 +101,13 @@ export class DefaultIdentifyService implements Startable {
100101
agentVersion: init.agentVersion ?? defaultValues.agentVersion
101102
}
102103

103-
// When a new connection happens, trigger identify
104-
components.events.addEventListener('connection:open', (evt) => {
105-
const connection = evt.detail
106-
this.identify(connection).catch(err => { log.error('error during identify trigged by connection:open', err) })
107-
})
104+
if (init.runOnConnectionOpen ?? defaultValues.runOnConnectionOpen) {
105+
// When a new connection happens, trigger identify
106+
components.events.addEventListener('connection:open', (evt) => {
107+
const connection = evt.detail
108+
this.identify(connection).catch(err => { log.error('error during identify trigged by connection:open', err) })
109+
})
110+
}
108111

109112
// When self peer record changes, trigger identify-push
110113
components.events.addEventListener('self:peer:update', (evt) => {
@@ -296,7 +299,7 @@ export class DefaultIdentifyService implements Startable {
296299
}
297300
}
298301

299-
async identify (connection: Connection, options: AbortOptions = {}): Promise<void> {
302+
async identify (connection: Connection, options: AbortOptions = {}): Promise<IdentifyResult> {
300303
const message = await this._identify(connection, options)
301304
const {
302305
publicKey,
@@ -344,6 +347,8 @@ export class DefaultIdentifyService implements Startable {
344347
}
345348

346349
this.events.safeDispatchEvent('peer:identify', { detail: result })
350+
351+
return result
347352
}
348353

349354
/**

packages/libp2p/src/identify/index.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import {
44
} from './consts.js'
55
import { DefaultIdentifyService } from './identify.js'
66
import { Identify } from './pb/message.js'
7-
import type { Libp2pEvents } from '@libp2p/interface'
7+
import type { AbortOptions, IdentifyResult, Libp2pEvents } from '@libp2p/interface'
88
import type { EventEmitter } from '@libp2p/interface/events'
99
import type { PeerId } from '@libp2p/interface/peer-id'
1010
import type { PeerStore } from '@libp2p/interface/peer-store'
11+
import type { Connection } from '@libp2p/interface/src/connection/index.js'
1112
import type { AddressManager } from '@libp2p/interface-internal/address-manager'
1213
import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager'
1314
import type { Registrar } from '@libp2p/interface-internal/registrar'
@@ -39,6 +40,11 @@ export interface IdentifyServiceInit {
3940
maxPushIncomingStreams?: number
4041
maxPushOutgoingStreams?: number
4142
maxObservedAddresses?: number
43+
44+
/**
45+
* Whether to automatically dial identify on newly opened connections (default: true)
46+
*/
47+
runOnConnectionOpen?: boolean
4248
}
4349

4450
export interface IdentifyServiceComponents {
@@ -60,6 +66,19 @@ export const multicodecs = {
6066

6167
export const Message = { Identify }
6268

63-
export function identifyService (init: IdentifyServiceInit = {}): (components: IdentifyServiceComponents) => DefaultIdentifyService {
69+
export interface IdentifyService {
70+
/**
71+
* due to the default limits on inbound/outbound streams for this protocol,
72+
* invoking this method when runOnConnectionOpen is true can lead to unpredictable results
73+
* as streams may be closed by the local or the remote node.
74+
* Please use with caution. If you find yourself needing to call this method to discover other peers that support your protocol,
75+
* you may be better off configuring a topology to be notified instead.
76+
*/
77+
identify: (connection: Connection, options?: AbortOptions) => Promise<IdentifyResult>
78+
79+
push: () => Promise<void>
80+
}
81+
82+
export function identifyService (init: IdentifyServiceInit = {}): (components: IdentifyServiceComponents) => IdentifyService {
6483
return (components) => new DefaultIdentifyService(components, init)
6584
}

packages/libp2p/test/connection-manager/direct.spec.ts

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,11 @@ import { stubInterface } from 'sinon-ts'
2222
import { defaultComponents, type Components } from '../../src/components.js'
2323
import { DefaultConnectionManager } from '../../src/connection-manager/index.js'
2424
import { codes as ErrorCodes } from '../../src/errors.js'
25-
import { identifyService } from '../../src/identify/index.js'
25+
import { type IdentifyService, identifyService } from '../../src/identify/index.js'
2626
import { createLibp2p } from '../../src/index.js'
2727
import { plaintext } from '../../src/insecure/index.js'
2828
import { DefaultTransportManager } from '../../src/transport-manager.js'
2929
import { createPeerId } from '../fixtures/creators/peer.js'
30-
import type { DefaultIdentifyService } from '../../src/identify/identify.js'
3130
import type { Libp2p } from '@libp2p/interface'
3231
import type { Connection } from '@libp2p/interface/connection'
3332
import type { PeerId } from '@libp2p/interface/peer-id'
@@ -342,7 +341,7 @@ describe('dialing (direct, WebSockets)', () => {
342341
})
343342

344343
describe('libp2p.dialer (direct, WebSockets)', () => {
345-
let libp2p: Libp2p<{ identify: unknown }>
344+
let libp2p: Libp2p<{ identify: IdentifyService }>
346345
let peerId: PeerId
347346

348347
beforeEach(async () => {
@@ -382,7 +381,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => {
382381
throw new Error('Identify service missing')
383382
}
384383

385-
const identifySpy = sinon.spy(libp2p.services.identify as DefaultIdentifyService, 'identify')
384+
const identifySpy = sinon.spy(libp2p.services.identify, 'identify')
386385
const peerStorePatchSpy = sinon.spy(libp2p.peerStore, 'patch')
387386
const connectionPromise = pEvent(libp2p, 'connection:open')
388387

@@ -402,6 +401,48 @@ describe('libp2p.dialer (direct, WebSockets)', () => {
402401
await libp2p.stop()
403402
})
404403

404+
it('should not run identify automatically after connecting', async () => {
405+
libp2p = await createLibp2p({
406+
peerId,
407+
transports: [
408+
webSockets({
409+
filter: filters.all
410+
})
411+
],
412+
streamMuxers: [
413+
yamux()
414+
],
415+
connectionEncryption: [
416+
plaintext()
417+
],
418+
services: {
419+
identify: identifyService({
420+
runOnConnectionOpen: false
421+
})
422+
},
423+
connectionGater: mockConnectionGater()
424+
})
425+
426+
if (libp2p.services.identify == null) {
427+
throw new Error('Identify service missing')
428+
}
429+
430+
const identifySpy = sinon.spy(libp2p.services.identify, 'identify')
431+
const connectionPromise = pEvent(libp2p, 'connection:open')
432+
433+
await libp2p.start()
434+
435+
const connection = await libp2p.dial(relayMultiaddr)
436+
expect(connection).to.exist()
437+
438+
// Wait for connection event to be emitted
439+
await connectionPromise
440+
441+
expect(identifySpy.callCount).to.equal(0)
442+
443+
await libp2p.stop()
444+
})
445+
405446
it('should be able to use hangup to close connections', async () => {
406447
libp2p = await createLibp2p({
407448
peerId,

packages/libp2p/test/identify/index.spec.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ describe('identify', () => {
105105
})
106106

107107
it('should be able to identify another peer', async () => {
108-
const localIdentify = new DefaultIdentifyService(localComponents, defaultInit)
109-
const remoteIdentify = new DefaultIdentifyService(remoteComponents, defaultInit)
108+
const localIdentify = identifyService(defaultInit)(localComponents)
109+
const remoteIdentify = identifyService(defaultInit)(remoteComponents)
110110

111111
await start(localIdentify)
112112
await start(remoteIdentify)
@@ -128,8 +128,8 @@ describe('identify', () => {
128128
})
129129

130130
it('should throw if identified peer is the wrong peer', async () => {
131-
const localIdentify = new DefaultIdentifyService(localComponents, defaultInit)
132-
const remoteIdentify = new DefaultIdentifyService(remoteComponents, defaultInit)
131+
const localIdentify = identifyService(defaultInit)(localComponents)
132+
const remoteIdentify = identifyService(defaultInit)(remoteComponents)
133133

134134
await start(localIdentify)
135135
await start(remoteIdentify)
@@ -191,8 +191,8 @@ describe('identify', () => {
191191
})
192192

193193
it('should time out during identify', async () => {
194-
const localIdentify = new DefaultIdentifyService(localComponents, defaultInit)
195-
const remoteIdentify = new DefaultIdentifyService(remoteComponents, defaultInit)
194+
const localIdentify = identifyService(defaultInit)(localComponents)
195+
const remoteIdentify = identifyService(defaultInit)(remoteComponents)
196196

197197
await start(localIdentify)
198198
await start(remoteIdentify)
@@ -237,10 +237,10 @@ describe('identify', () => {
237237
it('should limit incoming identify message sizes', async () => {
238238
const deferred = pDefer()
239239

240-
const remoteIdentify = new DefaultIdentifyService(remoteComponents, {
240+
const remoteIdentify = identifyService({
241241
...defaultInit,
242242
maxIdentifyMessageSize: 100
243-
})
243+
})(remoteComponents)
244244
await start(remoteIdentify)
245245

246246
const identifySpy = sinon.spy(remoteIdentify, 'identify')
@@ -283,10 +283,10 @@ describe('identify', () => {
283283
it('should time out incoming identify messages', async () => {
284284
const deferred = pDefer()
285285

286-
const remoteIdentify = new DefaultIdentifyService(remoteComponents, {
286+
const remoteIdentify = identifyService({
287287
...defaultInit,
288288
timeout: 100
289-
})
289+
})(remoteComponents)
290290
await start(remoteIdentify)
291291

292292
const identifySpy = sinon.spy(remoteIdentify, 'identify')
@@ -336,8 +336,8 @@ describe('identify', () => {
336336
})
337337

338338
it('should retain existing peer metadata', async () => {
339-
const localIdentify = new DefaultIdentifyService(localComponents, defaultInit)
340-
const remoteIdentify = new DefaultIdentifyService(remoteComponents, defaultInit)
339+
const localIdentify = identifyService(defaultInit)(localComponents)
340+
const remoteIdentify = identifyService(defaultInit)(remoteComponents)
341341

342342
await start(localIdentify)
343343
await start(remoteIdentify)
@@ -363,8 +363,8 @@ describe('identify', () => {
363363
})
364364

365365
it('should ignore older signed peer record', async () => {
366-
const localIdentify = new DefaultIdentifyService(localComponents, defaultInit)
367-
const remoteIdentify = new DefaultIdentifyService(remoteComponents, defaultInit)
366+
const localIdentify = identifyService(defaultInit)(localComponents)
367+
const remoteIdentify = identifyService(defaultInit)(remoteComponents)
368368

369369
await start(localIdentify)
370370
await start(remoteIdentify)

packages/libp2p/test/identify/push.spec.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ import {
2121
MULTICODEC_IDENTIFY,
2222
MULTICODEC_IDENTIFY_PUSH
2323
} from '../../src/identify/consts.js'
24-
import { DefaultIdentifyService } from '../../src/identify/identify.js'
24+
import { identifyService, type IdentifyServiceInit } from '../../src/identify/index.js'
2525
import { DefaultTransportManager } from '../../src/transport-manager.js'
26-
import type { IdentifyServiceInit } from '../../src/identify/index.js'
2726
import type { TransportManager } from '@libp2p/interface-internal/transport-manager'
2827

2928
const listenMaddrs = [multiaddr('/ip4/127.0.0.1/tcp/15002/ws')]
@@ -97,8 +96,8 @@ describe('identify (push)', () => {
9796
})
9897

9998
it('should be able to push identify updates to another peer', async () => {
100-
const localIdentify = new DefaultIdentifyService(localComponents, defaultInit)
101-
const remoteIdentify = new DefaultIdentifyService(remoteComponents, defaultInit)
99+
const localIdentify = identifyService(defaultInit)(localComponents)
100+
const remoteIdentify = identifyService(defaultInit)(remoteComponents)
102101

103102
await start(localIdentify)
104103
await start(remoteIdentify)
@@ -171,11 +170,11 @@ describe('identify (push)', () => {
171170

172171
it('should time out during push identify', async () => {
173172
let streamEnded = false
174-
const localIdentify = new DefaultIdentifyService(localComponents, {
173+
const localIdentify = identifyService({
175174
...defaultInit,
176175
timeout: 10
177-
})
178-
const remoteIdentify = new DefaultIdentifyService(remoteComponents, defaultInit)
176+
})(localComponents)
177+
const remoteIdentify = identifyService(defaultInit)(remoteComponents)
179178

180179
await start(localIdentify)
181180
await start(remoteIdentify)

packages/libp2p/test/identify/service.spec.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,16 @@ import sinon from 'sinon'
1010
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
1111
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
1212
import { AGENT_VERSION } from '../../src/identify/consts.js'
13-
import { identifyService } from '../../src/identify/index.js'
13+
import { type IdentifyService, identifyService } from '../../src/identify/index.js'
1414
import { createLibp2p } from '../../src/index.js'
1515
import { createBaseOptions } from '../fixtures/base-options.browser.js'
16-
import type { DefaultIdentifyService } from '../../src/identify/identify.js'
1716
import type { Libp2p, IdentifyResult } from '@libp2p/interface'
1817
import type { PeerId } from '@libp2p/interface/peer-id'
1918

2019
describe('identify', () => {
2120
let peerId: PeerId
22-
let libp2p: Libp2p<{ identify: unknown }>
23-
let remoteLibp2p: Libp2p<{ identify: unknown }>
21+
let libp2p: Libp2p<{ identify: IdentifyService }>
22+
let remoteLibp2p: Libp2p<{ identify: IdentifyService }>
2423
const remoteAddr = multiaddr(process.env.RELAY_MULTIADDR)
2524

2625
before(async () => {
@@ -55,7 +54,7 @@ describe('identify', () => {
5554
throw new Error('Identity service was not configured')
5655
}
5756

58-
const identityServiceIdentifySpy = sinon.spy(libp2p.services.identify as DefaultIdentifyService, 'identify')
57+
const identityServiceIdentifySpy = sinon.spy(libp2p.services.identify, 'identify')
5958

6059
const connection = await libp2p.dial(remoteAddr)
6160
expect(connection).to.exist()
@@ -110,7 +109,7 @@ describe('identify', () => {
110109
throw new Error('Identity service was not configured')
111110
}
112111

113-
const identityServiceIdentifySpy = sinon.spy(libp2p.services.identify as DefaultIdentifyService, 'identify')
112+
const identityServiceIdentifySpy = sinon.spy(libp2p.services.identify, 'identify')
114113

115114
const connection = await libp2p.dial(remoteAddr)
116115
expect(connection).to.exist()
@@ -143,8 +142,8 @@ describe('identify', () => {
143142
throw new Error('Identity service was not configured')
144143
}
145144

146-
const identityServiceIdentifySpy = sinon.spy(libp2p.services.identify as DefaultIdentifyService, 'identify')
147-
const identityServicePushSpy = sinon.spy(libp2p.services.identify as DefaultIdentifyService, 'push')
145+
const identityServiceIdentifySpy = sinon.spy(libp2p.services.identify, 'identify')
146+
const identityServicePushSpy = sinon.spy(libp2p.services.identify, 'push')
148147
const connectionPromise = pEvent(libp2p, 'connection:open')
149148
const connection = await libp2p.dial(remoteAddr)
150149

@@ -241,8 +240,8 @@ describe('identify', () => {
241240
throw new Error('Identity service was not configured')
242241
}
243242

244-
const identityServiceIdentifySpy = sinon.spy(libp2p.services.identify as DefaultIdentifyService, 'identify')
245-
const identityServicePushSpy = sinon.spy(libp2p.services.identify as DefaultIdentifyService, 'push')
243+
const identityServiceIdentifySpy = sinon.spy(libp2p.services.identify, 'identify')
244+
const identityServicePushSpy = sinon.spy(libp2p.services.identify, 'push')
246245
const connectionPromise = pEvent(libp2p, 'connection:open')
247246
const connection = await libp2p.dial(remoteAddr)
248247

0 commit comments

Comments
 (0)