Skip to content

Commit f4e8ac9

Browse files
committed
fix!: remove @libp2p/components
`@libp2p/components` is a choke-point for our dependency graph as it depends on every interface, meaning when one interface revs a major `@libp2p/components` major has to change too which means every module depending on it also needs a major. Switch instead to constructor injection of simple objects that let modules declare their dependencies on interfaces directly instead of indirectly via `@libp2p/components` Refs libp2p/js-libp2p-components#6 BREAKING CHANGE: modules no longer implement `Initializable` instead switching to constructor injection
1 parent 39e34a9 commit f4e8ac9

20 files changed

+623
-642
lines changed

package.json

+10-9
Original file line numberDiff line numberDiff line change
@@ -65,38 +65,39 @@
6565
},
6666
"homepage": "https://github.com/ChainSafe/js-libp2p-gossipsub#readme",
6767
"dependencies": {
68-
"@libp2p/components": "^3.0.1",
6968
"@libp2p/crypto": "^1.0.3",
7069
"@libp2p/interface-connection": "^3.0.1",
70+
"@libp2p/interface-connection-manager": "^1.3.0",
7171
"@libp2p/interface-keys": "^1.0.3",
7272
"@libp2p/interface-peer-id": "^1.0.4",
73-
"@libp2p/interface-pubsub": "^2.0.1",
73+
"@libp2p/interface-peer-store": "^1.2.2",
74+
"@libp2p/interface-pubsub": "^3.0.0",
7475
"@libp2p/interface-registrar": "^2.0.3",
7576
"@libp2p/interfaces": "^3.0.3",
7677
"@libp2p/logger": "^2.0.0",
7778
"@libp2p/peer-id": "^1.1.15",
7879
"@libp2p/peer-record": "^4.0.1",
79-
"@libp2p/pubsub": "^4.0.0",
80+
"@libp2p/pubsub": "^5.0.0",
8081
"@libp2p/topology": "^3.0.0",
8182
"abortable-iterator": "^4.0.2",
8283
"denque": "^1.5.0",
8384
"err-code": "^3.0.1",
8485
"it-length-prefixed": "^8.0.2",
8586
"it-pipe": "^2.0.4",
8687
"it-pushable": "^3.1.0",
87-
"multiformats": "^9.6.4",
88+
"multiformats": "^10.0.0",
8889
"protobufjs": "^6.11.2",
8990
"uint8arraylist": "^2.3.2",
90-
"uint8arrays": "^3.0.0"
91+
"uint8arrays": "^4.0.2"
9192
},
9293
"devDependencies": {
9394
"@chainsafe/as-sha256": "^0.2.4",
9495
"@dapplion/benchmark": "^0.2.2",
95-
"@libp2p/floodsub": "^4.0.0",
96-
"@libp2p/interface-mocks": "^6.0.1",
97-
"@libp2p/interface-pubsub-compliance-tests": "^2.0.2",
96+
"@libp2p/floodsub": "^5.0.0",
97+
"@libp2p/interface-mocks": "^7.0.1",
98+
"@libp2p/interface-pubsub-compliance-tests": "^4.0.0",
9899
"@libp2p/peer-id-factory": "^1.0.18",
99-
"@libp2p/peer-store": "^4.0.0",
100+
"@libp2p/peer-store": "^5.0.0",
100101
"@multiformats/multiaddr": "^11.0.0",
101102
"@types/node": "^17.0.21",
102103
"@typescript-eslint/eslint-plugin": "^3.0.2",

src/index.ts

+49-36
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import {
3838
ToSendGroupCount
3939
} from './metrics.js'
4040
import {
41-
MessageAcceptance,
4241
MsgIdFn,
4342
PublishConfig,
4443
TopicStr,
@@ -51,7 +50,6 @@ import {
5150
FastMsgIdFn,
5251
AddrInfo,
5352
DataTransform,
54-
TopicValidatorFn,
5553
rejectReasonFromAcceptance,
5654
MsgIdToStrFn,
5755
MessageId
@@ -61,7 +59,6 @@ import { msgIdFnStrictNoSign, msgIdFnStrictSign } from './utils/msgIdFn.js'
6159
import { computeAllPeersScoreWeights } from './score/scoreMetrics.js'
6260
import { getPublishConfigFromPeerId } from './utils/publishConfig.js'
6361
import type { GossipsubOptsSpec } from './config.js'
64-
import { Components, Initializable } from '@libp2p/components'
6562
import {
6663
Message,
6764
PublishResult,
@@ -70,14 +67,18 @@ import {
7067
PubSubInit,
7168
StrictNoSign,
7269
StrictSign,
73-
SubscriptionChangeData
70+
SubscriptionChangeData,
71+
TopicValidatorFn,
72+
TopicValidatorResult
7473
} from '@libp2p/interface-pubsub'
75-
import type { IncomingStreamData } from '@libp2p/interface-registrar'
74+
import type { IncomingStreamData, Registrar } from '@libp2p/interface-registrar'
7675
import { removeFirstNItemsFromSet, removeItemsFromSet } from './utils/set.js'
7776
import { pushable } from 'it-pushable'
7877
import { InboundStream, OutboundStream } from './stream.js'
7978
import { Uint8ArrayList } from 'uint8arraylist'
8079
import { decodeRpc, DecodeRPCLimits, defaultDecodeRpcLimits } from './message/decodeRpc.js'
80+
import { ConnectionManager } from '@libp2p/interface-connection-manager'
81+
import { PeerStore } from '@libp2p/interface-peer-store'
8182

8283
type ConnectionDirection = 'inbound' | 'outbound'
8384

@@ -209,7 +210,14 @@ interface AcceptFromWhitelistEntry {
209210
acceptUntil: number
210211
}
211212

212-
export class GossipSub extends EventEmitter<GossipsubEvents> implements Initializable, PubSub<GossipsubEvents> {
213+
export interface GossipSubComponents {
214+
peerId: PeerId
215+
peerStore: PeerStore
216+
registrar: Registrar
217+
connectionManager: ConnectionManager
218+
}
219+
220+
export class GossipSub extends EventEmitter<GossipsubEvents> implements PubSub<GossipsubEvents> {
213221
/**
214222
* The signature policy to follow by default
215223
*/
@@ -325,6 +333,12 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
325333
/** Peer score tracking */
326334
public readonly score: PeerScore
327335

336+
/**
337+
* Custom validator function per topic.
338+
* Must return or resolve quickly (< 100ms) to prevent causing penalties for late messages.
339+
* If you need to apply validation that may require longer times use `asyncValidation` option and callback the
340+
* validation result through `Gossipsub.reportValidationResult`
341+
*/
328342
public readonly topicValidators = new Map<TopicStr, TopicValidatorFn>()
329343

330344
/**
@@ -338,7 +352,7 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
338352
*/
339353
readonly gossipTracer: IWantTracer
340354

341-
private components = new Components()
355+
private readonly components: GossipSubComponents
342356

343357
private directPeerInitial: ReturnType<typeof setTimeout> | null = null
344358
private readonly log: Logger
@@ -361,7 +375,7 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
361375
cancel: () => void
362376
} | null = null
363377

364-
constructor(options: Partial<GossipsubOpts> = {}) {
378+
constructor(components: GossipSubComponents, options: Partial<GossipsubOpts> = {}) {
365379
super()
366380

367381
const opts = {
@@ -392,6 +406,7 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
392406
scoreThresholds: createPeerScoreThresholds(options.scoreThresholds)
393407
}
394408

409+
this.components = components
395410
this.decodeRpcLimits = opts.decodeRpcLimits ?? defaultDecodeRpcLimits
396411

397412
this.globalSignaturePolicy = opts.globalSignaturePolicy ?? StrictSign
@@ -473,7 +488,7 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
473488
/**
474489
* libp2p
475490
*/
476-
this.score = new PeerScore(this.opts.scoreParams, this.metrics, {
491+
this.score = new PeerScore(components, this.opts.scoreParams, this.metrics, {
477492
scoreCacheValidityMs: opts.heartbeatInterval
478493
})
479494

@@ -493,14 +508,6 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
493508

494509
// LIFECYCLE METHODS
495510

496-
/**
497-
* Pass libp2p components to interested system components
498-
*/
499-
async init(components: Components): Promise<void> {
500-
this.components = components
501-
this.score.init(components)
502-
}
503-
504511
/**
505512
* Mounts the gossipsub protocol onto the libp2p node and sends our
506513
* our subscriptions to every peer connected
@@ -513,7 +520,7 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
513520

514521
this.log('starting')
515522

516-
this.publishConfig = await getPublishConfigFromPeerId(this.globalSignaturePolicy, this.components.getPeerId())
523+
this.publishConfig = await getPublishConfigFromPeerId(this.globalSignaturePolicy, this.components.peerId)
517524

518525
// Create the outbound inflight queue
519526
// This ensures that outbound stream creation happens sequentially
@@ -527,11 +534,11 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
527534
// set direct peer addresses in the address book
528535
await Promise.all(
529536
this.opts.directPeers.map(async (p) => {
530-
await this.components.getPeerStore().addressBook.add(p.id, p.addrs)
537+
await this.components.peerStore.addressBook.add(p.id, p.addrs)
531538
})
532539
)
533540

534-
const registrar = this.components.getRegistrar()
541+
const registrar = this.components.registrar
535542
// Incoming streams
536543
// Called after a peer dials us
537544
await Promise.all(
@@ -611,7 +618,7 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
611618
this.status = { code: GossipStatusCode.stopped }
612619

613620
// unregister protocol and handlers
614-
const registrar = this.components.getRegistrar()
621+
const registrar = this.components.registrar
615622
registrarTopologyIds.forEach((id) => registrar.unregister(id))
616623

617624
this.outboundInflightQueue.end()
@@ -1059,7 +1066,7 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
10591066

10601067
// Dispatch the message to the user if we are subscribed to the topic
10611068
if (this.subscriptions.has(rpcMsg.topic)) {
1062-
const isFromSelf = this.components.getPeerId().equals(from)
1069+
const isFromSelf = this.components.peerId.equals(from)
10631070

10641071
if (!isFromSelf || this.opts.emitSelf) {
10651072
super.dispatchEvent(
@@ -1146,18 +1153,18 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
11461153
// to not penalize peers for long validation times.
11471154
const topicValidator = this.topicValidators.get(rpcMsg.topic)
11481155
if (topicValidator != null) {
1149-
let acceptance: MessageAcceptance
1156+
let acceptance: TopicValidatorResult
11501157
// Use try {} catch {} in case topicValidator() is synchronous
11511158
try {
1152-
acceptance = await topicValidator(msg.topic, msg, propagationSource)
1159+
acceptance = await topicValidator(propagationSource, msg)
11531160
} catch (e) {
11541161
const errCode = (e as { code: string }).code
1155-
if (errCode === constants.ERR_TOPIC_VALIDATOR_IGNORE) acceptance = MessageAcceptance.Ignore
1156-
if (errCode === constants.ERR_TOPIC_VALIDATOR_REJECT) acceptance = MessageAcceptance.Reject
1157-
else acceptance = MessageAcceptance.Ignore
1162+
if (errCode === constants.ERR_TOPIC_VALIDATOR_IGNORE) acceptance = TopicValidatorResult.Ignore
1163+
if (errCode === constants.ERR_TOPIC_VALIDATOR_REJECT) acceptance = TopicValidatorResult.Reject
1164+
else acceptance = TopicValidatorResult.Ignore
11581165
}
11591166

1160-
if (acceptance !== MessageAcceptance.Accept) {
1167+
if (acceptance !== TopicValidatorResult.Accept) {
11611168
return { code: MessageStatus.invalid, reason: rejectReasonFromAcceptance(acceptance), msgIdStr }
11621169
}
11631170
}
@@ -1619,7 +1626,7 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
16191626
this.log("bogus peer record obtained through px: peer ID %p doesn't match expected peer %p", eid, p)
16201627
return
16211628
}
1622-
if (!(await this.components.getPeerStore().addressBook.consumePeerRecord(envelope))) {
1629+
if (!(await this.components.peerStore.addressBook.consumePeerRecord(envelope))) {
16231630
this.log('bogus peer record obtained through px: could not add peer record to address book')
16241631
return
16251632
}
@@ -1643,9 +1650,9 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
16431650
private async connect(id: PeerIdStr): Promise<void> {
16441651
this.log('Initiating connection with %s', id)
16451652
const peerId = peerIdFromString(id)
1646-
const connection = await this.components.getConnectionManager().openConnection(peerId)
1653+
const connection = await this.components.connectionManager.openConnection(peerId)
16471654
for (const multicodec of this.multicodecs) {
1648-
for (const topology of this.components.getRegistrar().getTopologies(multicodec)) {
1655+
for (const topology of this.components.registrar.getTopologies(multicodec)) {
16491656
topology.onConnect(peerId, connection)
16501657
}
16511658
}
@@ -2006,12 +2013,12 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
20062013

20072014
// Dispatch the message to the user if we are subscribed to the topic
20082015
if (willSendToSelf) {
2009-
tosend.add(this.components.getPeerId().toString())
2016+
tosend.add(this.components.peerId.toString())
20102017

20112018
super.dispatchEvent(
20122019
new CustomEvent<GossipsubMessage>('gossipsub:message', {
20132020
detail: {
2014-
propagationSource: this.components.getPeerId(),
2021+
propagationSource: this.components.peerId,
20152022
msgId: msgIdStr,
20162023
msg
20172024
}
@@ -2047,8 +2054,8 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
20472054
*
20482055
* This should only be called once per message.
20492056
*/
2050-
reportMessageValidationResult(msgId: MsgIdStr, propagationSource: PeerId, acceptance: MessageAcceptance): void {
2051-
if (acceptance === MessageAcceptance.Accept) {
2057+
reportMessageValidationResult(msgId: MsgIdStr, propagationSource: PeerId, acceptance: TopicValidatorResult): void {
2058+
if (acceptance === TopicValidatorResult.Accept) {
20522059
const cacheEntry = this.mcache.validate(msgId)
20532060
this.metrics?.onReportValidationMcacheHit(cacheEntry !== null)
20542061

@@ -2340,7 +2347,7 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
23402347

23412348
return {
23422349
peerID: id.toBytes(),
2343-
signedPeerRecord: await this.components.getPeerStore().addressBook.getRawEnvelope(id)
2350+
signedPeerRecord: await this.components.peerStore.addressBook.getRawEnvelope(id)
23442351
}
23452352
})
23462353
)
@@ -2821,3 +2828,9 @@ export class GossipSub extends EventEmitter<GossipsubEvents> implements Initiali
28212828
metrics.registerScoreWeights(sw)
28222829
}
28232830
}
2831+
2832+
export function gossipsub(
2833+
init: Partial<GossipsubOpts> = {}
2834+
): (components: GossipSubComponents) => PubSub<GossipsubEvents> {
2835+
return (components: GossipSubComponents) => new GossipSub(components, init)
2836+
}

src/metrics.ts

+4-11
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
1+
import { TopicValidatorResult } from '@libp2p/interface-pubsub'
12
import type { IRPC } from './message/rpc.js'
23
import type { PeerScoreThresholds } from './score/peer-score-thresholds.js'
3-
import {
4-
MessageAcceptance,
5-
MessageStatus,
6-
PeerIdStr,
7-
RejectReason,
8-
RejectReasonObj,
9-
TopicStr,
10-
ValidateError
11-
} from './types.js'
4+
import { MessageStatus, PeerIdStr, RejectReason, RejectReasonObj, TopicStr, ValidateError } from './types.js'
125

136
/** Topic label as provided in `topicStrToLabel` */
147
export type TopicLabel = string
@@ -241,7 +234,7 @@ export function getMetrics(
241234
/** Message validation results for each topic.
242235
* Invalid == Reject?
243236
* = rust-libp2p `invalid_messages`, `accepted_messages`, `ignored_messages`, `rejected_messages` */
244-
asyncValidationResult: register.gauge<{ topic: TopicLabel; acceptance: MessageAcceptance }>({
237+
asyncValidationResult: register.gauge<{ topic: TopicLabel; acceptance: TopicValidatorResult }>({
245238
name: 'gossipsub_async_validation_result_total',
246239
help: 'Message validation result for each topic',
247240
labelNames: ['topic', 'acceptance']
@@ -543,7 +536,7 @@ export function getMetrics(
543536
this.asyncValidationMcacheHit.inc({ hit: hit ? 'hit' : 'miss' })
544537
},
545538

546-
onReportValidation(topicStr: TopicStr, acceptance: MessageAcceptance): void {
539+
onReportValidation(topicStr: TopicStr, acceptance: TopicValidatorResult): void {
547540
const topic = this.toTopic(topicStr)
548541
this.asyncValidationResult.inc({ topic: topic, acceptance })
549542
},

src/score/peer-score.ts

+14-9
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { MessageDeliveries, DeliveryRecordStatus } from './message-deliveries.js
55
import { logger } from '@libp2p/logger'
66
import { MsgIdStr, PeerIdStr, RejectReason, TopicStr, IPStr } from '../types.js'
77
import type { Metrics, ScorePenalty } from '../metrics.js'
8-
import { Components } from '@libp2p/components'
8+
import { ConnectionManager } from '@libp2p/interface-connection-manager'
99
import { peerIdFromString } from '@libp2p/peer-id'
1010

1111
const log = logger('libp2p:gossipsub:score')
@@ -28,6 +28,10 @@ interface ScoreCacheEntry {
2828

2929
export type PeerScoreStatsDump = Record<PeerIdStr, PeerStats>
3030

31+
export interface PeerScoreComponents {
32+
connectionManager: ConnectionManager
33+
}
34+
3135
export class PeerScore {
3236
/**
3337
* Per-peer stats for score calculation
@@ -49,19 +53,21 @@ export class PeerScore {
4953
_backgroundInterval?: ReturnType<typeof setInterval>
5054

5155
private readonly scoreCacheValidityMs: number
52-
private components = new Components()
56+
private readonly components: PeerScoreComponents
5357
private readonly computeScore: typeof computeScore
5458

55-
constructor(readonly params: PeerScoreParams, private readonly metrics: Metrics | null, opts: PeerScoreOpts) {
59+
constructor(
60+
components: PeerScoreComponents,
61+
readonly params: PeerScoreParams,
62+
private readonly metrics: Metrics | null,
63+
opts: PeerScoreOpts
64+
) {
5665
validatePeerScoreParams(params)
66+
this.components = components
5767
this.scoreCacheValidityMs = opts.scoreCacheValidityMs
5868
this.computeScore = opts.computeScore ?? computeScore
5969
}
6070

61-
init(components: Components): void {
62-
this.components = components
63-
}
64-
6571
get size(): number {
6672
return this.peerStats.size
6773
}
@@ -498,8 +504,7 @@ export class PeerScore {
498504
* Gets the current IPs for a peer.
499505
*/
500506
private getIPs(id: PeerIdStr): IPStr[] {
501-
return this.components
502-
.getConnectionManager()
507+
return this.components.connectionManager
503508
.getConnections(peerIdFromString(id))
504509
.map((c) => c.remoteAddr.toOptions().host)
505510
}

0 commit comments

Comments
 (0)