Skip to content

Commit 0d3ca9f

Browse files
authored
fix(gossipsub): cap prune backoff and process all prune entries (#3682)
Clamp the backoff accepted from a peer in a PRUNE message to one hour, matching rust-libp2p, and skip PRUNE entries for topics we are not meshed in instead of dropping the rest of the control message, matching go-libp2p and rust-libp2p. 🤖 Generated with AI assistance Co-authored-by: lodekeeper <lodekeeper@users.noreply.github.com>
1 parent 93f9eba commit 0d3ca9f

3 files changed

Lines changed: 56 additions & 2 deletions

File tree

‎packages/gossipsub/src/constants.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,12 @@ export const GossipsubPrunePeers = 16
198198
*/
199199
export const GossipsubPruneBackoff = minute
200200

201+
/**
202+
* GossipsubMaxPruneBackoff is the maximum backoff time we accept from a peer in a PRUNE message.
203+
* Larger values are clamped to this.
204+
*/
205+
export const GossipsubMaxPruneBackoff = 60 * minute
206+
201207
/**
202208
* Backoff to use when unsubscribing from a topic. Should not resubscribe to this topic before it expired.
203209
*/

‎packages/gossipsub/src/gossipsub.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1619,7 +1619,7 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
16191619

16201620
const peersInMesh = this.mesh.get(topicID)
16211621
if (peersInMesh == null) {
1622-
return
1622+
continue
16231623
}
16241624

16251625
this.log('PRUNE: Remove mesh link to %s in %s', id, topicID)
@@ -1631,7 +1631,7 @@ export class GossipSub extends TypedEventEmitter<GossipSubEvents> implements Typ
16311631

16321632
// is there a backoff specified by the peer? if so obey it
16331633
if (typeof backoff === 'number' && backoff > 0) {
1634-
this.doAddBackoff(id, topicID, backoff * 1000)
1634+
this.doAddBackoff(id, topicID, Math.min(backoff * 1000, constants.GossipsubMaxPruneBackoff))
16351635
} else {
16361636
this.addBackoff(id, topicID)
16371637
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { stop } from '@libp2p/interface'
2+
import { expect } from 'aegir/chai'
3+
import { GossipsubMaxPruneBackoff } from '../src/constants.ts'
4+
import { createComponents } from './utils/create-pubsub.ts'
5+
import { createPeerId } from './utils/index.ts'
6+
import type { GossipSubAndComponents } from './utils/create-pubsub.ts'
7+
8+
const TOPIC = 'prune-topic'
9+
10+
describe('handlePrune', () => {
11+
let node: GossipSubAndComponents
12+
let peer: string
13+
14+
beforeEach(async () => {
15+
node = await createComponents({ init: {} })
16+
peer = (await createPeerId()).toString()
17+
})
18+
19+
afterEach(async () => {
20+
await stop(node.pubsub, ...Object.values(node.components))
21+
})
22+
23+
it('caps the backoff accepted from a peer at GossipsubMaxPruneBackoff', async () => {
24+
const gs = node.pubsub as any
25+
gs.mesh.set(TOPIC, new Set([peer]))
26+
27+
const before = Date.now()
28+
await gs.handlePrune(peer, [{ topicID: TOPIC, peers: [], backoff: Number.MAX_SAFE_INTEGER }])
29+
const after = Date.now()
30+
31+
const expire = gs.backoff.get(TOPIC)?.get(peer)
32+
expect(expire).to.be.at.least(before + GossipsubMaxPruneBackoff)
33+
expect(expire).to.be.at.most(after + GossipsubMaxPruneBackoff)
34+
})
35+
36+
it('processes remaining entries after a topic that is not in the mesh', async () => {
37+
const gs = node.pubsub as any
38+
gs.mesh.set(TOPIC, new Set([peer]))
39+
40+
await gs.handlePrune(peer, [
41+
{ topicID: 'not-joined-topic', peers: [] },
42+
{ topicID: TOPIC, peers: [] }
43+
])
44+
45+
expect(node.pubsub.mesh.get(TOPIC)?.has(peer)).to.equal(false)
46+
expect(gs.backoff.get(TOPIC)?.has(peer)).to.equal(true)
47+
})
48+
})

0 commit comments

Comments
 (0)