Skip to content

Commit 29b47ad

Browse files
authored
fix: use keep-alive as a tag prefix (#2757)
To prevent components removing each other's keep-alive tag, treat it as a prefix.
1 parent e99e8f4 commit 29b47ad

File tree

8 files changed

+97
-15
lines changed

8 files changed

+97
-15
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
1+
/**
2+
* When a peer that is tagged with this prefix disconnects, we will attempt to
3+
* redial it, up to a limit.
4+
*
5+
* To allow multiple components to add/remove their own keep-alive tags without
6+
* accidentally overwriting those of other components, attach a unique suffix to
7+
* the tag, e.g. `keep-alive-circuit-relay` or `keep-alive-kad-dht`, etc.
8+
*/
19
export const KEEP_ALIVE = 'keep-alive'

packages/kad-dht/src/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
// until the year 2020 (a great time in the future). For that record to stick around
66
// it must be rebroadcasted more frequently than once every 'MaxRecordAge'
77

8+
import { KEEP_ALIVE } from '@libp2p/interface'
9+
810
export const second = 1000
911
export const minute = 60 * second
1012
export const hour = 60 * minute
@@ -51,3 +53,6 @@ export const TABLE_REFRESH_QUERY_TIMEOUT = 30 * second
5153

5254
// When a timeout is not specified, run a query for this long
5355
export const DEFAULT_QUERY_TIMEOUT = 180 * second
56+
57+
// used to ensure connections to our closest peers remain open
58+
export const KEEP_ALIVE_TAG = `${KEEP_ALIVE}-kad-dht`

packages/kad-dht/src/routing-table/closest-peers.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { KEEP_ALIVE } from '@libp2p/interface'
21
import { PeerSet } from '@libp2p/peer-collections'
2+
import { KEEP_ALIVE_TAG } from '../constants.js'
33
import { PeerDistanceList } from '../peer-distance-list.js'
44
import { convertPeerId } from '../utils.js'
55
import type { RoutingTable } from './index.js'
@@ -94,7 +94,7 @@ export class ClosestPeers implements Startable {
9494
[this.closeTagName]: {
9595
value: this.closeTagValue
9696
},
97-
[KEEP_ALIVE]: {
97+
[KEEP_ALIVE_TAG]: {
9898
value: 1
9999
}
100100
}
@@ -104,7 +104,7 @@ export class ClosestPeers implements Startable {
104104
await this.components.peerStore.merge(peerId, {
105105
tags: {
106106
[this.closeTagName]: undefined,
107-
[KEEP_ALIVE]: undefined
107+
[KEEP_ALIVE_TAG]: undefined
108108
}
109109
})
110110
})

packages/kad-dht/test/closest-peers.spec.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { generateKeyPair } from '@libp2p/crypto/keys'
2-
import { KEEP_ALIVE, start, stop } from '@libp2p/interface'
2+
import { start, stop } from '@libp2p/interface'
33
import { defaultLogger } from '@libp2p/logger'
44
import { peerIdFromPrivateKey, peerIdFromString } from '@libp2p/peer-id'
55
import { expect } from 'aegir/chai'
66
import delay from 'delay'
77
import { stubInterface } from 'sinon-ts'
88
import { xor } from 'uint8arrays/xor'
99
import { xorCompare } from 'uint8arrays/xor-compare'
10+
import { KEEP_ALIVE_TAG } from '../src/constants.js'
1011
import { ClosestPeers } from '../src/routing-table/closest-peers.js'
1112
import { convertPeerId } from '../src/utils.js'
1213
import type { RoutingTable } from '../src/routing-table/index.js'
@@ -112,7 +113,7 @@ function assertTagged (peerId: PeerId, peerStore: StubbedInstance<PeerStore>): v
112113
'kad-close': {
113114
value: 50
114115
},
115-
[KEEP_ALIVE]: {
116+
[KEEP_ALIVE_TAG]: {
116117
value: 1
117118
}
118119
}
@@ -123,7 +124,7 @@ function assertUnTagged (peerId: PeerId, peerStore: StubbedInstance<PeerStore>):
123124
expect(peerStore.merge.calledWith(peerId, {
124125
tags: {
125126
'kad-close': undefined,
126-
[KEEP_ALIVE]: undefined
127+
[KEEP_ALIVE_TAG]: undefined
127128
}
128129
})).to.be.true()
129130
}

packages/libp2p/src/connection-manager/reconnect-queue.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export class ReconnectQueue implements Startable {
6363

6464
const peer = await this.peerStore.get(peerId)
6565

66-
if (!peer.tags.has(KEEP_ALIVE)) {
66+
if (!hasKeepAliveTag(peer)) {
6767
return
6868
}
6969

@@ -94,8 +94,23 @@ export class ReconnectQueue implements Startable {
9494
}, {
9595
peerId
9696
})
97-
.catch(err => {
98-
this.log.error('failed to reconnect to %p', peerId, err)
97+
.catch(async err => {
98+
this.log.error('failed to reconnect to %p - %e', peerId, err)
99+
100+
const tags: Record<string, undefined> = {}
101+
102+
;[...peer.tags.keys()].forEach(key => {
103+
if (key.startsWith(KEEP_ALIVE)) {
104+
tags[key] = undefined
105+
}
106+
})
107+
108+
await this.peerStore.merge(peerId, {
109+
tags
110+
})
111+
})
112+
.catch(async err => {
113+
this.log.error('failed to remove keep-alive tag from %p - %e', peerId, err)
99114
})
100115
}
101116

@@ -108,9 +123,9 @@ export class ReconnectQueue implements Startable {
108123
void Promise.resolve()
109124
.then(async () => {
110125
const keepAlivePeers: Peer[] = await this.peerStore.all({
111-
filters: [(peer) => {
112-
return peer.tags.has(KEEP_ALIVE)
113-
}]
126+
filters: [
127+
(peer) => hasKeepAliveTag(peer)
128+
]
114129
})
115130

116131
await Promise.all(
@@ -132,3 +147,13 @@ export class ReconnectQueue implements Startable {
132147
this.queue.abort()
133148
}
134149
}
150+
151+
function hasKeepAliveTag (peer: Peer): boolean {
152+
for (const tag of peer.tags.keys()) {
153+
if (tag.startsWith(KEEP_ALIVE)) {
154+
return true
155+
}
156+
}
157+
158+
return false
159+
}

packages/libp2p/test/connection-manager/reconnect-queue.spec.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,43 @@ describe('reconnect queue', () => {
115115

116116
expect(components.connectionManager.openConnection.calledWith(nonKeepAlivePeer)).to.be.false()
117117
})
118+
119+
it('should remove KEEP_ALIVE tags when reconnecting fails', async () => {
120+
queue = new ReconnectQueue(components, {
121+
retries: 1,
122+
retryInterval: 10,
123+
backoffFactor: 1
124+
})
125+
126+
const keepAlivePeer = peerIdFromPrivateKey(await generateKeyPair('Ed25519'))
127+
128+
components.peerStore.all.resolves([])
129+
components.peerStore.get.withArgs(keepAlivePeer).resolves(
130+
stubInterface<Peer>({
131+
id: keepAlivePeer,
132+
tags: new Map([[KEEP_ALIVE, {
133+
value: 1
134+
}]])
135+
})
136+
)
137+
138+
await start(queue)
139+
140+
components.connectionManager.openConnection.withArgs(keepAlivePeer).rejects(new Error('Dial failed'))
141+
142+
components.events.safeDispatchEvent('peer:disconnect', new CustomEvent('peer:disconnect', {
143+
detail: keepAlivePeer
144+
}))
145+
146+
await pRetry(() => {
147+
expect(components.peerStore.merge.calledWith(keepAlivePeer, {
148+
tags: {
149+
[KEEP_ALIVE]: undefined
150+
}
151+
})).to.be.true()
152+
}, {
153+
retries: 5,
154+
factor: 1
155+
})
156+
})
118157
})

packages/transport-circuit-relay-v2/src/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { KEEP_ALIVE } from '@libp2p/interface'
2+
13
const second = 1000
24
const minute = 60 * second
35

@@ -40,6 +42,8 @@ export const RELAY_SOURCE_TAG = 'circuit-relay-source'
4042

4143
export const RELAY_TAG = 'circuit-relay-relay'
4244

45+
export const KEEP_ALIVE_TAG = `${KEEP_ALIVE}-circuit-relay`
46+
4347
// circuit v2 connection limits
4448
// https://github.com/libp2p/go-libp2p/blob/master/p2p/protocol/circuitv2/relay/resources.go#L61-L66
4549

packages/transport-circuit-relay-v2/src/transport/reservation-store.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { KEEP_ALIVE, TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
1+
import { TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
22
import { PeerMap } from '@libp2p/peer-collections'
33
import { createBloomFilter } from '@libp2p/utils/filters'
44
import { PeerQueue } from '@libp2p/utils/peer-queue'
55
import { multiaddr } from '@multiformats/multiaddr'
66
import { pbStream } from 'it-protobuf-stream'
77
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
8-
import { DEFAULT_MAX_RESERVATION_QUEUE_LENGTH, DEFAULT_RESERVATION_COMPLETION_TIMEOUT, DEFAULT_RESERVATION_CONCURRENCY, RELAY_TAG, RELAY_V2_HOP_CODEC } from '../constants.js'
8+
import { DEFAULT_MAX_RESERVATION_QUEUE_LENGTH, DEFAULT_RESERVATION_COMPLETION_TIMEOUT, DEFAULT_RESERVATION_CONCURRENCY, KEEP_ALIVE_TAG, RELAY_TAG, RELAY_V2_HOP_CODEC } from '../constants.js'
99
import { HopMessage, Status } from '../pb/index.js'
1010
import { getExpirationMilliseconds } from '../utils.js'
1111
import type { Reservation } from '../pb/index.js'
@@ -250,7 +250,7 @@ export class ReservationStore extends TypedEventEmitter<ReservationStoreEvents>
250250
value: 1,
251251
ttl: expiration
252252
},
253-
[KEEP_ALIVE]: {
253+
[KEEP_ALIVE_TAG]: {
254254
value: 1,
255255
ttl: expiration
256256
}

0 commit comments

Comments
 (0)