Skip to content

Commit a964fa9

Browse files
committed
Whitelist positive score peer to accept messages
1 parent a8a810d commit a964fa9

File tree

2 files changed

+125
-1
lines changed

2 files changed

+125
-1
lines changed

test/accept-from.spec.js

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
const {expect} = require('chai')
2+
const sinon = require('sinon')
3+
const {PeerScore} = require('../src/score')
4+
const Gossipsub = require('../src')
5+
const {
6+
createPeer,
7+
} = require('./utils')
8+
9+
describe('Gossipsub acceptFrom', () => {
10+
let gossipsub
11+
const sandbox = sinon.createSandbox()
12+
let scoreStub
13+
14+
beforeEach(async () => {
15+
sandbox.useFakeTimers()
16+
gossipsub = new Gossipsub(await createPeer({ started: false }), { emitSelf: true })
17+
scoreStub = sandbox.createStubInstance(PeerScore)
18+
gossipsub.score = scoreStub
19+
})
20+
21+
afterEach(() => {
22+
sandbox.restore()
23+
})
24+
25+
it('should only white list peer with positive score', () => {
26+
scoreStub.score.withArgs("peerA").returns(1000)
27+
gossipsub._acceptFrom("peerA")
28+
// 1st time, we have to compute score
29+
expect(scoreStub.score.withArgs("peerA").calledOnce).to.be.true
30+
// 2nd time, use a cached score since it's white listed
31+
gossipsub._acceptFrom("peerA")
32+
expect(scoreStub.score.withArgs("peerA").calledOnce).to.be.true
33+
})
34+
35+
it('should recompute score after 1s', () => {
36+
scoreStub.score.returns(1000)
37+
gossipsub._acceptFrom("peerA")
38+
// 1st time, we have to compute score
39+
expect(scoreStub.score.withArgs("peerA").calledOnce).to.be.true
40+
gossipsub._acceptFrom("peerA")
41+
expect(scoreStub.score.withArgs("peerA").calledOnce).to.be.true
42+
43+
// after 1s
44+
sandbox.clock.tick(1001)
45+
46+
gossipsub._acceptFrom("peerA")
47+
expect(scoreStub.score.withArgs("peerA").calledTwice).to.be.true
48+
})
49+
50+
it('should recompute score after max messages accepted', () => {
51+
scoreStub.score.returns(1000)
52+
gossipsub._acceptFrom("peerA")
53+
// 1st time, we have to compute score
54+
expect(scoreStub.score.withArgs("peerA").calledOnce).to.be.true
55+
56+
for (let i = 0; i < 128; i++) {
57+
gossipsub._acceptFrom("peerA")
58+
}
59+
expect(scoreStub.score.withArgs("peerA").calledOnce).to.be.true
60+
61+
// max messages reached
62+
gossipsub._acceptFrom("peerA")
63+
expect(scoreStub.score.withArgs("peerA").calledTwice).to.be.true
64+
})
65+
66+
it('should NOT white list peer with negative score', () => {
67+
// peerB is not white listed since score is negative
68+
scoreStub.score.withArgs("peerB").returns(-1)
69+
gossipsub._acceptFrom("peerB")
70+
// 1st time, we have to compute score
71+
expect(scoreStub.score.withArgs("peerB").calledOnce).to.be.true
72+
// 2nd time, still have to compute score since it's NOT white listed
73+
gossipsub._acceptFrom("peerB")
74+
expect(scoreStub.score.withArgs("peerB").calledTwice).to.be.true
75+
})
76+
77+
78+
79+
})

ts/index.ts

+46-1
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,22 @@ interface GossipOptions extends GossipInputOptions {
8484
scoreThresholds: PeerScoreThresholds
8585
}
8686

87+
const ACCEPT_REQUEST_WHITE_LIST_THRESHOLD_SCORE = 0
88+
const ACCEPT_REQUEST_WHITE_LIST_MAX_MESSAGES = 128
89+
const ACCEPT_REQUEST_WHITE_LIST_DURATION_MS = 1000
90+
91+
interface AcceptRequestWhiteListEntry {
92+
/** max number of messages accepted after a score is calculated */
93+
messagesAccepted: number
94+
/** have to recompute score after this time */
95+
whitelistedTill: number
96+
}
97+
8798
class Gossipsub extends Pubsub {
8899
peers: Map<string, PeerStreams>
89100
direct: Set<string>
90101
seenCache: SimpleTimeCache
102+
acceptRequestsWhitelist: Map<string, AcceptRequestWhiteListEntry>
91103
topics: Map<string, Set<string>>
92104
mesh: Map<string, Set<string>>
93105
fanout: Map<string, Set<string>>
@@ -182,6 +194,13 @@ class Gossipsub extends Pubsub {
182194
*/
183195
this.direct = new Set(opts.directPeers.map(p => p.id.toB58String()))
184196

197+
/**
198+
* Map of peer id and AcceptRequestWhileListEntry
199+
*
200+
* @type {Map<string, AcceptRequestWhiteListEntry}
201+
*/
202+
this.acceptRequestsWhitelist = new Map()
203+
185204
// set direct peer addresses in the address book
186205
opts.directPeers.forEach(p => {
187206
libp2p.peerStore.addressBook.add(p.id, p.addrs)
@@ -449,7 +468,33 @@ class Gossipsub extends Pubsub {
449468
* @returns {boolean}
450469
*/
451470
_acceptFrom (id: string): boolean {
452-
return this.direct.has(id) || this.score.score(id) >= this._options.scoreThresholds.graylistThreshold
471+
if (this.direct.has(id)) {
472+
return true
473+
}
474+
475+
const now = Date.now()
476+
const entry = this.acceptRequestsWhitelist.get(id)
477+
478+
if (entry &&
479+
entry.messagesAccepted < ACCEPT_REQUEST_WHITE_LIST_MAX_MESSAGES &&
480+
entry.whitelistedTill >= now) {
481+
entry.messagesAccepted += 1
482+
return true
483+
}
484+
485+
const score = this.score.score(id)
486+
if (score >= ACCEPT_REQUEST_WHITE_LIST_THRESHOLD_SCORE) {
487+
// peer is unlikely to be able to drop its score to `graylistThreshold`
488+
// after 128 messages or 1s
489+
this.acceptRequestsWhitelist.set(id, {
490+
messagesAccepted: 0,
491+
whitelistedTill: now + ACCEPT_REQUEST_WHITE_LIST_DURATION_MS
492+
})
493+
} else {
494+
this.acceptRequestsWhitelist.delete(id)
495+
}
496+
497+
return score >= this._options.scoreThresholds.graylistThreshold
453498
}
454499

455500
/**

0 commit comments

Comments
 (0)