Skip to content

Commit dac2859

Browse files
committed
fix: do not overwrite addresses on identify push when none are sent
During identify-push in response to a protocol update go-libp2p does not send a signed peer record or the current list of listen addresess. Protobuf does not let us differenciate between an empty list and no list items at all because the field is just `repeated` - an absense of repeated fields doesn't mean the list was omitted. When the listen address list is empty, preserve any existing addresses.
1 parent dd400cd commit dac2859

File tree

2 files changed

+71
-1
lines changed

2 files changed

+71
-1
lines changed

packages/libp2p/src/identify/identify.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,17 @@ export class DefaultIdentifyService implements Startable, IdentifyService {
490490
peer.metadata.set('ProtocolVersion', uint8ArrayFromString(message.protocolVersion))
491491
}
492492

493+
// if the remote has not sent addresses, preserve the existing ones.
494+
// this can happen during an identify-push triggered by a supported protocol
495+
// change but the protobuf format makes it impossible to know if the remote
496+
// was telling us they have no addresses (unlikely) or if they meant to send
497+
// a partial update and omit the list entirely
498+
if (peer.addresses.length === 0) {
499+
// @ts-expect-error addresses is not optional
500+
delete peer.addresses
501+
}
502+
503+
log('patching %p with', peer)
493504
await this.peerStore.patch(connection.remotePeer, peer)
494505

495506
const result: IdentifyResult = {

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

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import { TypedEventEmitter } from '@libp2p/interface/events'
55
import { start, stop } from '@libp2p/interface/startable'
6-
import { mockConnectionGater, mockRegistrar, mockUpgrader, connectionPair } from '@libp2p/interface-compliance-tests/mocks'
6+
import { mockConnectionGater, mockRegistrar, mockUpgrader, connectionPair, mockStream } from '@libp2p/interface-compliance-tests/mocks'
77
import { createEd25519PeerId } from '@libp2p/peer-id-factory'
88
import { PeerRecord, RecordEnvelope } from '@libp2p/peer-record'
99
import { PersistentPeerStore } from '@libp2p/peer-store'
@@ -33,6 +33,9 @@ import { Identify } from '../../src/identify/pb/message.js'
3333
import { DefaultTransportManager } from '../../src/transport-manager.js'
3434
import type { IncomingStreamData } from '@libp2p/interface-internal/registrar'
3535
import type { TransportManager } from '@libp2p/interface-internal/transport-manager'
36+
import { pushable } from 'it-pushable'
37+
import type { Duplex, Source } from 'it-stream-types'
38+
import type { Connection } from '@libp2p/interface/src/connection/index.js'
3639

3740
const listenMaddrs = [multiaddr('/ip4/127.0.0.1/tcp/15002/ws')]
3841

@@ -504,4 +507,60 @@ describe('identify', () => {
504507
}])
505508
expect(peer.id.publicKey).to.equalBytes(remoteComponents.peerId.publicKey)
506509
})
510+
511+
it('should not overwrite addresses when none are sent', async () => {
512+
const localIdentify = new DefaultIdentifyService(localComponents, defaultInit)
513+
await start(localIdentify)
514+
515+
const duplex: Duplex<any, Source<any>, any> = {
516+
source: pushable(),
517+
sink: async (source) => {
518+
await drain(source)
519+
}
520+
}
521+
522+
duplex.source.push(lp.encode.single(Identify.encode({
523+
listenAddrs: [
524+
multiaddr('/ip4/127.0.0.1/tcp/4001').bytes
525+
],
526+
protocols: [
527+
'/proto/1'
528+
]
529+
})))
530+
531+
await localIdentify._handlePush({
532+
stream: mockStream(duplex),
533+
connection: stubInterface<Connection>({
534+
remotePeer: remoteComponents.peerId
535+
})
536+
})
537+
538+
const peerData = await localComponents.peerStore.get(remoteComponents.peerId)
539+
expect(peerData.addresses[0].multiaddr.toString()).to.equal('/ip4/127.0.0.1/tcp/4001')
540+
expect(peerData.protocols).to.deep.equal(['/proto/1'])
541+
542+
const updateDuplex: Duplex<any, Source<any>, any> = {
543+
source: pushable(),
544+
sink: async (source) => {
545+
await drain(source)
546+
}
547+
}
548+
549+
updateDuplex.source.push(lp.encode.single(Identify.encode({
550+
protocols: [
551+
'/proto/2'
552+
]
553+
})))
554+
555+
await localIdentify._handlePush({
556+
stream: mockStream(updateDuplex),
557+
connection: stubInterface<Connection>({
558+
remotePeer: remoteComponents.peerId
559+
})
560+
})
561+
562+
const updatedPeerData = await localComponents.peerStore.get(remoteComponents.peerId)
563+
expect(updatedPeerData.addresses[0].multiaddr.toString()).to.equal('/ip4/127.0.0.1/tcp/4001')
564+
expect(updatedPeerData.protocols).to.deep.equal(['/proto/2'])
565+
})
507566
})

0 commit comments

Comments
 (0)