@libp2p/floodsub 11.0.0 → 11.0.1-39e2e541a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/floodsub.ts CHANGED
@@ -1,19 +1,17 @@
1
1
  import { InvalidMessageError, NotStartedError, InvalidParametersError, serviceCapabilities, serviceDependencies } from '@libp2p/interface'
2
2
  import { PeerMap, PeerSet } from '@libp2p/peer-collections'
3
- import { pipe } from 'it-pipe'
4
3
  import { TypedEventEmitter } from 'main-event'
5
4
  import Queue from 'p-queue'
6
5
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
7
6
  import { SimpleTimeCache } from './cache.js'
8
7
  import { pubSubSymbol } from './constants.ts'
9
8
  import { RPC } from './message/rpc.js'
10
- import { PeerStreams, PeerStreams as PeerStreamsImpl } from './peer-streams.js'
9
+ import { PeerStreams } from './peer-streams.js'
11
10
  import { signMessage, verifySignature } from './sign.js'
12
- import { toMessage, ensureArray, noSignMsgId, msgId, toRpcMessage, randomSeqno } from './utils.js'
11
+ import { toMessage, noSignMsgId, msgId, toRpcMessage, randomSeqno } from './utils.js'
13
12
  import { protocol, StrictNoSign, TopicValidatorResult, StrictSign } from './index.js'
14
13
  import type { FloodSubComponents, FloodSubEvents, FloodSubInit, FloodSub as FloodSubInterface, Message, PublishResult, SubscriptionChangeData, TopicValidatorFn } from './index.js'
15
- import type { Logger, Connection, PeerId, Stream, Topology } from '@libp2p/interface'
16
- import type { Uint8ArrayList } from 'uint8arraylist'
14
+ import type { Logger, Connection, PeerId, Stream } from '@libp2p/interface'
17
15
 
18
16
  export interface PubSubRPCMessage {
19
17
  from?: Uint8Array
@@ -74,10 +72,10 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
74
72
  */
75
73
  public topicValidators: Map<string, TopicValidatorFn>
76
74
  public queue: Queue
77
- public protocols: string[]
75
+ public protocol: string
78
76
  public components: FloodSubComponents
79
77
 
80
- private _registrarTopologyIds: string[] | undefined
78
+ private _registrarTopologyId: string | undefined
81
79
  private readonly maxInboundStreams: number
82
80
  private readonly maxOutboundStreams: number
83
81
  public seenCache: SimpleTimeCache<boolean>
@@ -87,7 +85,7 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
87
85
 
88
86
  this.log = components.logger.forComponent('libp2p:floodsub')
89
87
  this.components = components
90
- this.protocols = ensureArray(init.protocols ?? protocol)
88
+ this.protocol = init.protocol ?? protocol
91
89
  this.started = false
92
90
  this.topics = new Map()
93
91
  this.subscriptions = new Set()
@@ -134,23 +132,19 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
134
132
 
135
133
  this.log('starting')
136
134
 
137
- const registrar = this.components.registrar
138
135
  // Incoming streams
139
136
  // Called after a peer dials us
140
- await Promise.all(this.protocols.map(async multicodec => {
141
- await registrar.handle(multicodec, this._onIncomingStream, {
142
- maxInboundStreams: this.maxInboundStreams,
143
- maxOutboundStreams: this.maxOutboundStreams
144
- })
145
- }))
137
+ await this.components.registrar.handle(this.protocol, this._onIncomingStream, {
138
+ maxInboundStreams: this.maxInboundStreams,
139
+ maxOutboundStreams: this.maxOutboundStreams
140
+ })
146
141
 
147
142
  // register protocol with topology
148
- // Topology callbacks called on connection manager changes
149
- const topology: Topology = {
143
+ // Topology callbacks called after identify has run on a new connection
144
+ this._registrarTopologyId = await this.components.registrar.register(this.protocol, {
150
145
  onConnect: this._onPeerConnected,
151
146
  onDisconnect: this._onPeerDisconnected
152
- }
153
- this._registrarTopologyIds = await Promise.all(this.protocols.map(async multicodec => registrar.register(multicodec, topology)))
147
+ })
154
148
 
155
149
  this.log('started')
156
150
  this.started = true
@@ -167,15 +161,11 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
167
161
  const registrar = this.components.registrar
168
162
 
169
163
  // unregister protocol and handlers
170
- if (this._registrarTopologyIds != null) {
171
- this._registrarTopologyIds?.forEach(id => {
172
- registrar.unregister(id)
173
- })
164
+ if (this._registrarTopologyId != null) {
165
+ registrar.unregister(this._registrarTopologyId)
174
166
  }
175
167
 
176
- await Promise.all(this.protocols.map(async multicodec => {
177
- await registrar.unhandle(multicodec)
178
- }))
168
+ await registrar.unhandle(this.protocol)
179
169
 
180
170
  this.log('stopping')
181
171
  for (const peerStreams of this.peers.values()) {
@@ -196,18 +186,8 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
196
186
  * On an inbound stream opened
197
187
  */
198
188
  protected _onIncomingStream (stream: Stream, connection: Connection): void {
199
- const peerId = connection.remotePeer
200
-
201
- if (stream.protocol == null) {
202
- stream.abort(new Error('Stream was not multiplexed'))
203
- return
204
- }
205
-
206
- const peer = this.addPeer(peerId, stream.protocol)
207
- const inboundStream = peer.attachInboundStream(stream)
208
-
209
- this.processMessages(peerId, inboundStream, peer)
210
- .catch(err => { this.log(err) })
189
+ const peerStreams = this.addPeer(connection.remotePeer, stream)
190
+ peerStreams.attachInboundStream(stream)
211
191
  }
212
192
 
213
193
  /**
@@ -217,23 +197,20 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
217
197
  this.log('connected %p', peerId)
218
198
 
219
199
  // if this connection is already in use for pubsub, ignore it
220
- if (conn.streams.find(stream => stream.direction === 'outbound' && stream.protocol != null && this.protocols.includes(stream.protocol)) != null) {
221
- this.log('outbound pubsub streams already present on connection from %p', peerId)
200
+ if (conn.streams.find(stream => stream.direction === 'outbound' && stream.protocol === this.protocol)) {
201
+ this.log('outbound pubsub stream already present on connection from %p', peerId)
222
202
  return
223
203
  }
224
204
 
225
- const stream = await conn.newStream(this.protocols)
226
-
227
- if (stream.protocol == null) {
228
- stream.abort(new Error('Stream was not multiplexed'))
229
- return
230
- }
231
-
232
- const peer = this.addPeer(peerId, stream.protocol)
233
- await peer.attachOutboundStream(stream)
205
+ const stream = await conn.newStream(this.protocol)
206
+ const peerStreams = this.addPeer(peerId, stream)
207
+ peerStreams.attachOutboundStream(stream)
234
208
 
235
209
  // Immediately send my own subscriptions to the newly established conn
236
- this.send(peerId, { subscriptions: Array.from(this.subscriptions).map(sub => sub.toString()), subscribe: true })
210
+ this.send(peerId, {
211
+ subscriptions: Array.from(this.subscriptions).map(sub => sub.toString()),
212
+ subscribe: true
213
+ })
237
214
  }
238
215
 
239
216
  /**
@@ -247,7 +224,7 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
247
224
  /**
248
225
  * Notifies the router that a peer has been connected
249
226
  */
250
- addPeer (peerId: PeerId, protocol: string): PeerStreams {
227
+ addPeer (peerId: PeerId, stream: Stream): PeerStreams {
251
228
  const existing = this.peers.get(peerId)
252
229
 
253
230
  // If peer streams already exists, do nothing
@@ -258,12 +235,42 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
258
235
  // else create a new peer streams
259
236
  this.log('new peer %p', peerId)
260
237
 
261
- const peerStreams: PeerStreams = new PeerStreamsImpl(this.components, {
262
- id: peerId,
263
- protocol
264
- })
238
+ const peerStreams = new PeerStreams(peerId)
265
239
 
266
240
  this.peers.set(peerId, peerStreams)
241
+ peerStreams.addEventListener('message', (evt) => {
242
+ const rpcMsg = evt.detail
243
+ const messages: PubSubRPCMessage[] = []
244
+
245
+ for (const msg of (rpcMsg.messages ?? [])) {
246
+ if (msg.from == null || msg.data == null || msg.topic == null) {
247
+ this.log('message from %p was missing from, data or topic fields, dropping', peerId)
248
+ continue
249
+ }
250
+
251
+ messages.push({
252
+ from: msg.from,
253
+ data: msg.data,
254
+ topic: msg.topic,
255
+ sequenceNumber: msg.sequenceNumber ?? undefined,
256
+ signature: msg.signature ?? undefined,
257
+ key: msg.key ?? undefined
258
+ })
259
+ }
260
+
261
+ // Since processRpc may be overridden entirely in unsafe ways,
262
+ // the simplest/safest option here is to wrap in a function and capture all errors
263
+ // to prevent a top-level unhandled exception
264
+ // This processing of rpc messages should happen without awaiting full validation/execution of prior messages
265
+ this.processRpc(peerStreams, {
266
+ subscriptions: (rpcMsg.subscriptions ?? []).map(sub => ({
267
+ subscribe: Boolean(sub.subscribe),
268
+ topic: sub.topic ?? ''
269
+ })),
270
+ messages
271
+ })
272
+ .catch(err => { this.log(err) })
273
+ })
267
274
  peerStreams.addEventListener('close', () => this._removePeer(peerId), {
268
275
  once: true
269
276
  })
@@ -274,7 +281,7 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
274
281
  /**
275
282
  * Notifies the router that a peer has been disconnected
276
283
  */
277
- protected _removePeer (peerId: PeerId): PeerStreams | undefined {
284
+ protected _removePeer (peerId: PeerId): void {
278
285
  const peerStreams = this.peers.get(peerId)
279
286
  if (peerStreams == null) {
280
287
  return
@@ -291,84 +298,27 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
291
298
  for (const peers of this.topics.values()) {
292
299
  peers.delete(peerId)
293
300
  }
294
-
295
- return peerStreams
296
- }
297
-
298
- // MESSAGE METHODS
299
-
300
- /**
301
- * Responsible for processing each RPC message received by other peers.
302
- */
303
- async processMessages (peerId: PeerId, stream: AsyncIterable<Uint8ArrayList>, peerStreams: PeerStreams): Promise<void> {
304
- try {
305
- await pipe(
306
- stream,
307
- async (source) => {
308
- for await (const data of source) {
309
- const rpcMsg = this.decodeRpc(data)
310
- const messages: PubSubRPCMessage[] = []
311
-
312
- for (const msg of (rpcMsg.messages ?? [])) {
313
- if (msg.from == null || msg.data == null || msg.topic == null) {
314
- this.log('message from %p was missing from, data or topic fields, dropping', peerId)
315
- continue
316
- }
317
-
318
- messages.push({
319
- from: msg.from,
320
- data: msg.data,
321
- topic: msg.topic,
322
- sequenceNumber: msg.sequenceNumber ?? undefined,
323
- signature: msg.signature ?? undefined,
324
- key: msg.key ?? undefined
325
- })
326
- }
327
-
328
- // Since processRpc may be overridden entirely in unsafe ways,
329
- // the simplest/safest option here is to wrap in a function and capture all errors
330
- // to prevent a top-level unhandled exception
331
- // This processing of rpc messages should happen without awaiting full validation/execution of prior messages
332
- this.processRpc(peerId, peerStreams, {
333
- subscriptions: (rpcMsg.subscriptions ?? []).map(sub => ({
334
- subscribe: Boolean(sub.subscribe),
335
- topic: sub.topic ?? ''
336
- })),
337
- messages
338
- })
339
- .catch(err => { this.log(err) })
340
- }
341
- }
342
- )
343
- } catch (err: any) {
344
- this._onPeerDisconnected(peerStreams.id, err)
345
- }
346
301
  }
347
302
 
348
303
  /**
349
304
  * Handles an rpc request from a peer
350
305
  */
351
- async processRpc (from: PeerId, peerStreams: PeerStreams, rpc: PubSubRPC): Promise<boolean> {
352
- if (!this.acceptFrom(from)) {
353
- this.log('received message from unacceptable peer %p', from)
354
- return false
355
- }
356
-
357
- this.log('rpc from %p', from)
306
+ async processRpc (peerStream: PeerStreams, rpc: PubSubRPC): Promise<boolean> {
307
+ this.log('rpc from %p', peerStream.peerId)
358
308
 
359
309
  const { subscriptions, messages } = rpc
360
310
 
361
311
  if (subscriptions != null && subscriptions.length > 0) {
362
- this.log('subscription update from %p', from)
312
+ this.log('subscription update from %p', peerStream.peerId)
363
313
 
364
314
  // update peer subscriptions
365
315
  subscriptions.forEach((subOpt) => {
366
- this.processRpcSubOpt(from, subOpt)
316
+ this.processRpcSubOpt(peerStream.peerId, subOpt)
367
317
  })
368
318
 
369
319
  super.dispatchEvent(new CustomEvent<SubscriptionChangeData>('subscription-change', {
370
320
  detail: {
371
- peerId: peerStreams.id,
321
+ peerId: peerStream.peerId,
372
322
  subscriptions: subscriptions.map(({ topic, subscribe }) => ({
373
323
  topic: `${topic ?? ''}`,
374
324
  subscribe: Boolean(subscribe)
@@ -378,7 +328,7 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
378
328
  }
379
329
 
380
330
  if (messages != null && messages.length > 0) {
381
- this.log('messages from %p', from)
331
+ this.log('messages from %p', peerStream.peerId)
382
332
 
383
333
  this.queue.addAll(messages.map(message => async () => {
384
334
  if (message.topic == null || (!this.subscriptions.has(message.topic) && !this.canRelayMessage)) {
@@ -389,7 +339,7 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
389
339
  try {
390
340
  const msg = await toMessage(message)
391
341
 
392
- await this.processMessage(from, msg)
342
+ await this.processMessage(peerStream.peerId, msg)
393
343
  } catch (err: any) {
394
344
  this.log.error(err)
395
345
  }
@@ -492,30 +442,6 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
492
442
  }
493
443
  }
494
444
 
495
- /**
496
- * Whether to accept a message from a peer
497
- * Override to create a gray list
498
- */
499
- acceptFrom (id: PeerId): boolean {
500
- return true
501
- }
502
-
503
- /**
504
- * Decode Uint8Array into an RPC object.
505
- * This can be override to use a custom router protobuf.
506
- */
507
- decodeRpc (bytes: Uint8Array | Uint8ArrayList): PubSubRPC {
508
- return RPC.decode(bytes)
509
- }
510
-
511
- /**
512
- * Encode RPC object into a Uint8Array.
513
- * This can be override to use a custom router protobuf.
514
- */
515
- encodeRpc (rpc: PubSubRPC): Uint8Array {
516
- return RPC.encode(rpc)
517
- }
518
-
519
445
  /**
520
446
  * Encode RPC object into a Uint8Array.
521
447
  * This can be override to use a custom router protobuf.
@@ -540,21 +466,15 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
540
466
  * Send an rpc object to a peer
541
467
  */
542
468
  sendRpc (peer: PeerId, rpc: PubSubRPC): void {
543
- const peerStreams = this.peers.get(peer)
469
+ const peerStream = this.peers.get(peer)
544
470
 
545
- if (peerStreams == null) {
471
+ if (peerStream == null) {
546
472
  this.log.error('Cannot send RPC to %p as there are no streams to it available', peer)
547
473
 
548
474
  return
549
475
  }
550
476
 
551
- if (!peerStreams.isWritable) {
552
- this.log.error('Cannot send RPC to %p as there is no outbound stream to it available', peer)
553
-
554
- return
555
- }
556
-
557
- peerStreams.write(this.encodeRpc(rpc))
477
+ peerStream.write(rpc)
558
478
  }
559
479
 
560
480
  /**
@@ -741,14 +661,22 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
741
661
  throw new Error('Pubsub has not started')
742
662
  }
743
663
 
664
+ if (this.subscriptions.has(topic)) {
665
+ // already subscribed
666
+ return
667
+ }
668
+
744
669
  this.log('subscribe to topic: %s', topic)
745
670
 
746
- if (!this.subscriptions.has(topic)) {
747
- this.subscriptions.add(topic)
671
+ this.subscriptions.add(topic)
748
672
 
749
- for (const peerId of this.peers.keys()) {
750
- this.send(peerId, { subscriptions: [topic], subscribe: true })
751
- }
673
+ for (const peerId of this.peers.keys()) {
674
+ this.send(peerId, {
675
+ subscriptions: [
676
+ topic
677
+ ],
678
+ subscribe: true
679
+ })
752
680
  }
753
681
  }
754
682
 
@@ -760,16 +688,22 @@ export class FloodSub extends TypedEventEmitter<FloodSubEvents> implements Flood
760
688
  throw new Error('Pubsub is not started')
761
689
  }
762
690
 
763
- const wasSubscribed = this.subscriptions.has(topic)
691
+ if (!this.subscriptions.has(topic)) {
692
+ // not subscribed
693
+ return
694
+ }
764
695
 
765
- this.log('unsubscribe from %s - am subscribed %s', topic, wasSubscribed)
696
+ this.log('unsubscribe from %s', topic)
766
697
 
767
- if (wasSubscribed) {
768
- this.subscriptions.delete(topic)
698
+ this.subscriptions.delete(topic)
769
699
 
770
- for (const peerId of this.peers.keys()) {
771
- this.send(peerId, { subscriptions: [topic], subscribe: false })
772
- }
700
+ for (const peerId of this.peers.keys()) {
701
+ this.send(peerId, {
702
+ subscriptions: [
703
+ topic
704
+ ],
705
+ subscribe: false
706
+ })
773
707
  }
774
708
  }
775
709
 
package/src/index.ts CHANGED
@@ -34,6 +34,7 @@
34
34
 
35
35
  import { pubSubSymbol } from './constants.ts'
36
36
  import { FloodSub as FloodSubClass } from './floodsub.js'
37
+ import type { PubSubRPC } from './floodsub.js'
37
38
  import type { ComponentLogger, PeerId, PrivateKey, PublicKey, TypedEventTarget } from '@libp2p/interface'
38
39
  import type { Registrar } from '@libp2p/interface-internal'
39
40
 
@@ -119,9 +120,8 @@ export interface TopicValidatorFn {
119
120
  (peer: PeerId, message: Message): TopicValidatorResult | Promise<TopicValidatorResult>
120
121
  }
121
122
 
122
- export interface PeerStreamEvents {
123
- 'stream:inbound': CustomEvent<never>
124
- 'stream:outbound': CustomEvent<never>
123
+ export interface PeerStreamsEvents {
124
+ message: CustomEvent<PubSubRPC>
125
125
  close: CustomEvent<never>
126
126
  }
127
127
 
@@ -145,9 +145,9 @@ export interface FloodSub extends TypedEventTarget<FloodSubEvents> {
145
145
  globalSignaturePolicy: typeof StrictSign | typeof StrictNoSign
146
146
 
147
147
  /**
148
- * A list of multicodecs that contain the pubsub protocol name.
148
+ * The protocol name used by FloodSub
149
149
  */
150
- protocols: string[]
150
+ protocol: string
151
151
 
152
152
  /**
153
153
  * Pubsub routers support message validators per topic, which will validate the message
@@ -257,9 +257,9 @@ export interface FloodSubInit {
257
257
  /**
258
258
  * Override the protocol registered with the registrar
259
259
  *
260
- * @default ['/floodsub/1.0.0']
260
+ * @default '/floodsub/1.0.0'
261
261
  */
262
- protocols?: string[]
262
+ protocol?: string
263
263
 
264
264
  /**
265
265
  * defines how signatures should be handled