@peerbit/stream 1.0.20 → 2.0.1

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/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { EventEmitter, CustomEvent } from "@libp2p/interface/events";
1
+ import { TypedEventEmitter, CustomEvent } from "@libp2p/interface";
2
2
  import { pipe } from "it-pipe";
3
3
  import Queue from "p-queue";
4
4
  import type { PeerId } from "@libp2p/interface/peer-id";
@@ -9,9 +9,7 @@ import type { Stream } from "@libp2p/interface/connection";
9
9
  import { Uint8ArrayList } from "uint8arraylist";
10
10
  import { abortableSource } from "abortable-iterator";
11
11
  import * as lp from "it-length-prefixed";
12
- import { Routes } from "./routes.js";
13
- import { multiaddr } from "@multiformats/multiaddr";
14
- import { PeerMap } from "./peer-map.js";
12
+ import { MAX_ROUTE_DISTANCE, Routes } from "./routes.js";
15
13
  import type {
16
14
  IncomingStreamData,
17
15
  Registrar
@@ -20,39 +18,62 @@ import type { AddressManager } from "@libp2p/interface-internal/address-manager"
20
18
  import type { ConnectionManager } from "@libp2p/interface-internal/connection-manager";
21
19
 
22
20
  import { PeerStore } from "@libp2p/interface/peer-store";
21
+ import pDefer from "p-defer";
22
+
23
+ import { AbortError, delay, TimeoutError, waitFor } from "@peerbit/time";
23
24
 
24
- import { delay, TimeoutError, waitFor, waitForAsync } from "@peerbit/time";
25
25
  import {
26
- Ed25519PublicKey,
27
26
  getKeypairFromPeerId,
28
27
  getPublicKeyFromPeerId,
29
28
  PublicSignKey,
30
- SignatureWithKey
29
+ sha256Base64,
30
+ SignatureWithKey,
31
+ toBase64
31
32
  } from "@peerbit/crypto";
32
33
 
34
+ import { multiaddr } from "@multiformats/multiaddr";
35
+ import { Components } from "libp2p/components";
36
+ import type { TypedEventTarget } from "@libp2p/interface";
37
+
33
38
  export type SignaturePolicy = "StictSign" | "StrictNoSign";
39
+
34
40
  import { logger } from "./logger.js";
35
- import { Cache } from "@peerbit/cache";
41
+
36
42
  export { logger };
43
+
44
+ import { Cache } from "@peerbit/cache";
37
45
  import type { Libp2pEvents } from "@libp2p/interface";
46
+ import { ready } from "@peerbit/crypto";
38
47
  import {
39
- PeerEvents,
40
48
  Message as Message,
41
- Goodbye,
42
- Hello,
43
49
  DataMessage,
44
- Ping,
45
- PingPong,
46
- Pong,
47
50
  getMsgId,
48
- WaitForPeer
51
+ WaitForPeer,
52
+ ACK,
53
+ SeekDelivery,
54
+ AcknowledgeDelivery,
55
+ SilentDelivery,
56
+ MessageHeader,
57
+ Goodbye,
58
+ StreamEvents,
59
+ TracedDelivery,
60
+ AnyWhere
49
61
  } from "@peerbit/stream-interface";
62
+
63
+ import { MultiAddrinfo } from "@peerbit/stream-interface";
64
+ import { MovingAverageTracker } from "./metrics.js";
65
+
66
+ const logError = (e?: { message: string }) => {
67
+ return logger.error(e?.message);
68
+ };
50
69
  export interface PeerStreamsInit {
51
70
  peerId: PeerId;
52
71
  publicKey: PublicSignKey;
53
72
  protocol: string;
54
73
  connId: string;
55
74
  }
75
+ const DEFAULT_SEEK_MESSAGE_REDUDANCY = 2;
76
+ const DEFAULT_SILENT_MESSAGE_REDUDANCY = 1;
56
77
 
57
78
  const isWebsocketConnection = (c: Connection) =>
58
79
  c.remoteAddr.protoNames().find((x) => x === "ws" || x === "wss");
@@ -62,10 +83,30 @@ export interface PeerStreamEvents {
62
83
  "stream:outbound": CustomEvent<never>;
63
84
  close: CustomEvent<never>;
64
85
  }
86
+
87
+ const SEEK_DELIVERY_TIMEOUT = 15e3;
88
+ const MAX_DATA_LENGTH = 1e7 + 1000; // 10 mb and some metadata
89
+ const MAX_QUEUED_BYTES = MAX_DATA_LENGTH * 50;
90
+
91
+ const DEFAULT_PRUNE_CONNECTIONS_INTERVAL = 2e4;
92
+ const DEFAULT_MIN_CONNECTIONS = 2;
93
+ const DEFAULT_MAX_CONNECTIONS = 300;
94
+
95
+ const DEFAULT_PRUNED_CONNNECTIONS_TIMEOUT = 30 * 1000;
96
+
97
+ const ROUTE_UPDATE_DELAY_FACTOR = 3e4;
98
+
99
+ type WithTo = {
100
+ to?: (string | PublicSignKey | PeerId)[] | Set<string>;
101
+ };
102
+
103
+ type WithMode = {
104
+ mode?: SilentDelivery | SeekDelivery | AcknowledgeDelivery | AnyWhere;
105
+ };
65
106
  /**
66
107
  * Thin wrapper around a peer's inbound / outbound pubsub streams
67
108
  */
68
- export class PeerStreams extends EventEmitter<PeerStreamEvents> {
109
+ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
69
110
  public counter = 0;
70
111
  public readonly peerId: PeerId;
71
112
  public readonly publicKey: PublicSignKey;
@@ -73,7 +114,7 @@ export class PeerStreams extends EventEmitter<PeerStreamEvents> {
73
114
  /**
74
115
  * Write stream - it's preferable to use the write method
75
116
  */
76
- public outboundStream?: Pushable<Uint8ArrayList>;
117
+ public outboundStream?: Pushable<Uint8Array>;
77
118
  /**
78
119
  * Read stream
79
120
  */
@@ -93,10 +134,11 @@ export class PeerStreams extends EventEmitter<PeerStreamEvents> {
93
134
 
94
135
  private closed: boolean;
95
136
 
96
- public pingJob: { resolve: () => void; abort: () => void };
97
- public pingLatency: number | undefined;
98
-
99
137
  public connId: string;
138
+
139
+ public seekedOnce: boolean;
140
+
141
+ private usedBandWidthTracker: MovingAverageTracker;
100
142
  constructor(init: PeerStreamsInit) {
101
143
  super();
102
144
 
@@ -107,6 +149,7 @@ export class PeerStreams extends EventEmitter<PeerStreamEvents> {
107
149
  this.closed = false;
108
150
  this.connId = init.connId;
109
151
  this.counter = 1;
152
+ this.usedBandWidthTracker = new MovingAverageTracker();
110
153
  }
111
154
 
112
155
  /**
@@ -123,6 +166,10 @@ export class PeerStreams extends EventEmitter<PeerStreamEvents> {
123
166
  return Boolean(this.outboundStream);
124
167
  }
125
168
 
169
+ get usedBandwidth() {
170
+ return this.usedBandWidthTracker.value;
171
+ }
172
+
126
173
  /**
127
174
  * Send a message to this peer.
128
175
  * Throws if there is no `stream` to write to available.
@@ -132,12 +179,20 @@ export class PeerStreams extends EventEmitter<PeerStreamEvents> {
132
179
  logger.error("No writable connection to " + this.peerId.toString());
133
180
  throw new Error("No writable connection to " + this.peerId.toString());
134
181
  }
182
+
183
+ this.usedBandWidthTracker.add(data.byteLength);
184
+
135
185
  this.outboundStream.push(
136
- data instanceof Uint8Array ? new Uint8ArrayList(data) : data
186
+ data instanceof Uint8Array ? data : data.subarray()
137
187
  );
138
188
  }
139
189
 
140
190
  async waitForWrite(bytes: Uint8Array | Uint8ArrayList) {
191
+ if (this.closed) {
192
+ logger.error("Failed to send to stream: " + this.peerId + ". Closed");
193
+ return;
194
+ }
195
+
141
196
  if (!this.isWritable) {
142
197
  // Catch the event where the outbound stream is attach, but also abort if we shut down
143
198
  const outboundPromise = new Promise<void>((rs, rj) => {
@@ -197,7 +252,7 @@ export class PeerStreams extends EventEmitter<PeerStreamEvents> {
197
252
  this._rawInboundStream = stream;
198
253
  this.inboundStream = abortableSource(
199
254
  pipe(this._rawInboundStream, (source) =>
200
- lp.decode(source, { maxDataLength: 100001000 })
255
+ lp.decode(source, { maxDataLength: MAX_DATA_LENGTH })
201
256
  ),
202
257
  this.inboundAbortController.signal,
203
258
  {
@@ -221,10 +276,8 @@ export class PeerStreams extends EventEmitter<PeerStreamEvents> {
221
276
  const _prevStream = this.outboundStream;
222
277
 
223
278
  this._rawOutboundStream = stream;
224
- this.pingJob?.abort();
225
-
226
- this.outboundStream = pushable<Uint8ArrayList>({
227
- objectMode: true,
279
+ this.outboundStream = pushable<Uint8Array>({
280
+ objectMode: false,
228
281
  onEnd: () => {
229
282
  return stream.close().then(() => {
230
283
  if (this._rawOutboundStream === stream) {
@@ -240,9 +293,7 @@ export class PeerStreams extends EventEmitter<PeerStreamEvents> {
240
293
  this.outboundStream,
241
294
  (source) => lp.encode(source),
242
295
  this._rawOutboundStream
243
- ).catch((err: Error) => {
244
- logger.error(err);
245
- });
296
+ ).catch(logError);
246
297
 
247
298
  // Emit if the connection is new
248
299
  this.dispatchEvent(new CustomEvent("stream:outbound"));
@@ -275,59 +326,65 @@ export class PeerStreams extends EventEmitter<PeerStreamEvents> {
275
326
  await this._rawInboundStream?.close();
276
327
  }
277
328
 
278
- this.pingJob?.abort();
279
- this.pingLatency = undefined;
280
-
281
- //this.dispatchEvent(new CustomEvent('close'))
329
+ this.dispatchEvent(new CustomEvent("close"));
282
330
  this._rawOutboundStream = undefined;
283
331
  this.outboundStream = undefined;
332
+
284
333
  this._rawInboundStream = undefined;
285
334
  this.inboundStream = undefined;
286
335
  }
287
336
  }
288
337
 
289
- export interface MessageEvents {
290
- message: CustomEvent<Message>;
291
- }
292
-
293
- export interface StreamEvents extends PeerEvents, MessageEvents {
294
- data: CustomEvent<DataMessage>;
295
- }
296
-
297
- export type ConnectionManagerOptions = {
298
- autoDial?: boolean;
299
- retryDelay?: number;
338
+ type DialerOptions = {
339
+ retryDelay: number;
340
+ };
341
+ type PrunerOptions = {
342
+ interval: number; // how often to check for pruning
343
+ bandwidth?: number; // Mbps, unlimited if unset
344
+ maxBuffer?: number; // max queued bytes until pruning
345
+ connectionTimeout: number; // how long a pruned connection should be treated as non wanted
300
346
  };
347
+ type ConnectionManagerOptions = {
348
+ minConnections: number;
349
+ maxConnections: number;
350
+ dialer?: DialerOptions;
351
+ pruner?: PrunerOptions;
352
+ };
353
+
301
354
  export type DirectStreamOptions = {
302
355
  canRelayMessage?: boolean;
303
- emitSelf?: boolean;
304
356
  messageProcessingConcurrency?: number;
305
357
  maxInboundStreams?: number;
306
358
  maxOutboundStreams?: number;
307
359
  signaturePolicy?: SignaturePolicy;
308
- pingInterval?: number | null;
309
- connectionManager?: ConnectionManagerOptions;
360
+ connectionManager?: ConnectionManagerArguments;
361
+ routeSeekInterval?: number;
362
+ seekTimeout?: number;
310
363
  };
311
364
 
312
- import { Components } from "libp2p/components";
313
-
314
365
  export interface DirectStreamComponents extends Components {
315
366
  peerId: PeerId;
316
367
  addressManager: AddressManager;
317
368
  registrar: Registrar;
318
369
  connectionManager: ConnectionManager;
319
370
  peerStore: PeerStore;
320
- events: EventEmitter<Libp2pEvents>;
371
+ events: TypedEventTarget<Libp2pEvents>;
321
372
  }
322
373
 
374
+ export type ConnectionManagerArguments =
375
+ | (Partial<Pick<ConnectionManagerOptions, "minConnections">> &
376
+ Partial<Pick<ConnectionManagerOptions, "maxConnections">> & {
377
+ pruner?: Partial<PrunerOptions> | false;
378
+ } & { dialer?: Partial<DialerOptions> | false })
379
+ | false;
380
+
323
381
  export abstract class DirectStream<
324
382
  Events extends { [s: string]: any } = StreamEvents
325
383
  >
326
- extends EventEmitter<Events>
384
+ extends TypedEventEmitter<Events>
327
385
  implements WaitForPeer
328
386
  {
329
387
  public peerId: PeerId;
330
- public peerIdStr: string;
331
388
  public publicKey: PublicSignKey;
332
389
  public publicKeyHash: string;
333
390
  public sign: (bytes: Uint8Array) => Promise<SignatureWithKey>;
@@ -337,9 +394,8 @@ export abstract class DirectStream<
337
394
  /**
338
395
  * Map of peer streams
339
396
  */
340
- public peers: PeerMap<PeerStreams>;
397
+ public peers: Map<string, PeerStreams>;
341
398
  public peerKeyHashToPublicKey: Map<string, PublicSignKey>;
342
- public peerIdToPublicKey: Map<string, PublicSignKey>;
343
399
  public routes: Routes;
344
400
  /**
345
401
  * If router can relay received messages, even if not subscribed
@@ -350,22 +406,32 @@ export abstract class DirectStream<
350
406
  */
351
407
 
352
408
  public signaturePolicy: SignaturePolicy;
353
- public emitSelf: boolean;
354
409
  public queue: Queue;
355
410
  public multicodecs: string[];
356
- public seenCache: Cache;
357
- public earlyGoodbyes: Map<string, Goodbye>;
358
- public helloMap: Map<string, Map<string, Hello>>; // key is hash of publicKey, value is map whey key is hash of signature bytes, and value is latest Hello
359
- public multiaddrsMap: Map<string, string[]>;
411
+ public seenCache: Cache<number>;
360
412
  private _registrarTopologyIds: string[] | undefined;
361
413
  private readonly maxInboundStreams?: number;
362
414
  private readonly maxOutboundStreams?: number;
363
- private topology: any;
364
- private pingJobPromise: any;
365
- private pingJob: any;
366
- private pingInterval: number | null;
367
- private connectionManagerOptions: ConnectionManagerOptions;
368
- private recentDials: Cache<string>;
415
+ connectionManagerOptions: ConnectionManagerOptions;
416
+ private recentDials?: Cache<string>;
417
+ private healthChecks: Map<string, ReturnType<typeof setTimeout>>;
418
+ private pruneConnectionsTimeout: ReturnType<typeof setInterval>;
419
+ private prunedConnectionsCache?: Cache<string>;
420
+ routeSeekInterval: number;
421
+ seekTimeout: number;
422
+ closeController: AbortController;
423
+ private _ackCallbacks: Map<
424
+ string,
425
+ {
426
+ promise: Promise<void>;
427
+ callback: (
428
+ ack: ACK,
429
+ messageThrough: PeerStreams,
430
+ messageFrom?: PeerStreams
431
+ ) => void;
432
+ timeout: ReturnType<typeof setTimeout>;
433
+ }
434
+ > = new Map();
369
435
 
370
436
  constructor(
371
437
  readonly components: DirectStreamComponents,
@@ -375,46 +441,82 @@ export abstract class DirectStream<
375
441
  super();
376
442
  const {
377
443
  canRelayMessage = false,
378
- emitSelf = false,
379
444
  messageProcessingConcurrency = 10,
380
- pingInterval = 10 * 1000,
381
445
  maxInboundStreams,
382
446
  maxOutboundStreams,
383
447
  signaturePolicy = "StictSign",
384
- connectionManager = { autoDial: true }
448
+ connectionManager,
449
+ routeSeekInterval = ROUTE_UPDATE_DELAY_FACTOR,
450
+ seekTimeout = SEEK_DELIVERY_TIMEOUT
385
451
  } = options || {};
386
452
 
387
453
  const signKey = getKeypairFromPeerId(components.peerId);
454
+ this.seekTimeout = seekTimeout;
388
455
  this.sign = signKey.sign.bind(signKey);
389
456
  this.peerId = components.peerId;
390
- this.peerIdStr = components.peerId.toString();
391
457
  this.publicKey = signKey.publicKey;
392
458
  this.publicKeyHash = signKey.publicKey.hashcode();
393
459
  this.multicodecs = multicodecs;
394
460
  this.started = false;
395
461
  this.peers = new Map<string, PeerStreams>();
396
- this.helloMap = new Map();
397
- this.multiaddrsMap = new Map();
398
462
  this.routes = new Routes(this.publicKeyHash);
399
463
  this.canRelayMessage = canRelayMessage;
400
- this.emitSelf = emitSelf;
464
+ this.healthChecks = new Map();
465
+ this.routeSeekInterval = routeSeekInterval;
401
466
  this.queue = new Queue({ concurrency: messageProcessingConcurrency });
402
- this.earlyGoodbyes = new Map();
403
467
  this.maxInboundStreams = maxInboundStreams;
404
468
  this.maxOutboundStreams = maxOutboundStreams;
405
- this.seenCache = new Cache({ max: 1e3, ttl: 10 * 60 * 1e3 });
469
+ this.seenCache = new Cache({ max: 1e6, ttl: 10 * 60 * 1e3 });
470
+
406
471
  this.peerKeyHashToPublicKey = new Map();
407
- this.peerIdToPublicKey = new Map();
408
- this.pingInterval = pingInterval;
409
472
  this._onIncomingStream = this._onIncomingStream.bind(this);
410
473
  this.onPeerConnected = this.onPeerConnected.bind(this);
411
474
  this.onPeerDisconnected = this.onPeerDisconnected.bind(this);
412
475
  this.signaturePolicy = signaturePolicy;
413
- this.connectionManagerOptions = connectionManager;
414
- this.recentDials = new Cache({
415
- ttl: connectionManager.retryDelay || 60 * 1000,
416
- max: 1e3
417
- });
476
+
477
+ if (connectionManager === false || connectionManager === null) {
478
+ this.connectionManagerOptions = {
479
+ maxConnections: Number.MAX_SAFE_INTEGER,
480
+ minConnections: 0,
481
+ dialer: undefined,
482
+ pruner: undefined
483
+ };
484
+ } else {
485
+ this.connectionManagerOptions = {
486
+ maxConnections: DEFAULT_MAX_CONNECTIONS,
487
+ minConnections: DEFAULT_MIN_CONNECTIONS,
488
+ ...connectionManager,
489
+ dialer:
490
+ connectionManager?.dialer !== false &&
491
+ connectionManager?.dialer !== null
492
+ ? { retryDelay: 60 * 1000, ...connectionManager?.dialer }
493
+ : undefined,
494
+ pruner:
495
+ connectionManager?.pruner !== false &&
496
+ connectionManager?.pruner !== null
497
+ ? {
498
+ connectionTimeout: DEFAULT_PRUNED_CONNNECTIONS_TIMEOUT,
499
+ interval: DEFAULT_PRUNE_CONNECTIONS_INTERVAL,
500
+ maxBuffer: MAX_QUEUED_BYTES,
501
+ ...connectionManager?.pruner
502
+ }
503
+ : undefined
504
+ };
505
+ }
506
+
507
+ this.recentDials = this.connectionManagerOptions.dialer
508
+ ? new Cache({
509
+ ttl: this.connectionManagerOptions.dialer.retryDelay,
510
+ max: 1e3
511
+ })
512
+ : undefined;
513
+
514
+ this.prunedConnectionsCache = this.connectionManagerOptions.pruner
515
+ ? new Cache({
516
+ max: 1e6,
517
+ ttl: this.connectionManagerOptions.pruner.connectionTimeout
518
+ })
519
+ : undefined;
418
520
  }
419
521
 
420
522
  async start() {
@@ -422,18 +524,12 @@ export abstract class DirectStream<
422
524
  return;
423
525
  }
424
526
 
425
- logger.debug("starting");
527
+ await ready;
426
528
 
427
- // register protocol with topology
428
- // Topology callbacks called on connection manager changes
429
- this._registrarTopologyIds = await Promise.all(
430
- this.multicodecs.map((multicodec) =>
431
- this.components.registrar.register(multicodec, {
432
- onConnect: this.onPeerConnected.bind(this),
433
- onDisconnect: this.onPeerDisconnected.bind(this)
434
- })
435
- )
436
- );
529
+ this.closeController = new AbortController();
530
+ this.started = true;
531
+
532
+ logger.debug("starting");
437
533
 
438
534
  // Incoming streams
439
535
  // Called after a peer dials us
@@ -441,19 +537,23 @@ export abstract class DirectStream<
441
537
  this.multicodecs.map((multicodec) =>
442
538
  this.components.registrar.handle(multicodec, this._onIncomingStream, {
443
539
  maxInboundStreams: this.maxInboundStreams,
444
- maxOutboundStreams: this.maxOutboundStreams
540
+ maxOutboundStreams: this.maxOutboundStreams,
541
+ runOnTransientConnection: false
445
542
  })
446
543
  )
447
544
  );
448
545
 
449
- // TODO remove/modify when https://github.com/libp2p/js-libp2p/issues/2036 is resolved
450
- this.components.events.addEventListener("connection:open", (e) => {
451
- if (e.detail.multiplexer === "/webrtc") {
452
- this.onPeerConnected(e.detail.remotePeer, e.detail);
453
- }
454
- });
455
-
456
- this.started = true;
546
+ // register protocol with topology
547
+ // Topology callbacks called on connection manager changes
548
+ this._registrarTopologyIds = await Promise.all(
549
+ this.multicodecs.map((multicodec) =>
550
+ this.components.registrar.register(multicodec, {
551
+ onConnect: this.onPeerConnected.bind(this),
552
+ onDisconnect: this.onPeerDisconnected.bind(this),
553
+ notifyOnTransient: false
554
+ })
555
+ )
556
+ );
457
557
 
458
558
  // All existing connections are like new ones for us. To deduplication on remotes so we only resuse one connection for this protocol (we could be connected with many connections)
459
559
  const peerToConnections: Map<string, Connection[]> = new Map();
@@ -476,37 +576,24 @@ export abstract class DirectStream<
476
576
  }
477
577
  }
478
578
 
479
- await this.onPeerConnected(conn.remotePeer, conn, { fromExisting: true });
579
+ await this.onPeerConnected(conn.remotePeer, conn);
480
580
  }
481
-
482
- const pingJob = async () => {
483
- // TODO don't use setInterval but waitFor previous done to be done
484
- await this.pingJobPromise;
485
- const promises: Promise<any>[] = [];
486
- this.peers.forEach((peer) => {
487
- promises.push(
488
- this.ping(peer).catch((e) => {
489
- if (e instanceof TimeoutError) {
490
- // Ignore
491
- } else {
492
- logger.error(e);
581
+ if (this.connectionManagerOptions.pruner) {
582
+ const pruneConnectionsLoop = () => {
583
+ if (!this.connectionManagerOptions.pruner) {
584
+ return;
585
+ }
586
+ this.pruneConnectionsTimeout = setTimeout(() => {
587
+ this.maybePruneConnections().finally(() => {
588
+ if (!this.started) {
589
+ return;
493
590
  }
494
- })
495
- );
496
- });
497
- promises.push(this.hello()); // Repetedly say hello to everyone to create traces in the network to measure latencies
498
- this.pingJobPromise = Promise.all(promises)
499
- .catch((e) => {
500
- logger.error(e?.message);
501
- })
502
- .finally(() => {
503
- if (!this.started || !this.pingInterval) {
504
- return;
505
- }
506
- this.pingJob = setTimeout(pingJob, this.pingInterval);
507
- });
508
- };
509
- pingJob();
591
+ pruneConnectionsLoop();
592
+ });
593
+ }, this.connectionManagerOptions.pruner.interval);
594
+ };
595
+ pruneConnectionsLoop();
596
+ }
510
597
  }
511
598
 
512
599
  /**
@@ -517,9 +604,9 @@ export abstract class DirectStream<
517
604
  return;
518
605
  }
519
606
  this.started = false;
607
+ this.closeController.abort();
520
608
 
521
- clearTimeout(this.pingJob);
522
- await this.pingJobPromise;
609
+ clearTimeout(this.pruneConnectionsTimeout);
523
610
 
524
611
  await Promise.all(
525
612
  this.multicodecs.map((x) => this.components.registrar.unhandle(x))
@@ -529,6 +616,11 @@ export abstract class DirectStream<
529
616
  for (const peerStreams of this.peers.values()) {
530
617
  await peerStreams.close();
531
618
  }
619
+ for (const [k, v] of this.healthChecks) {
620
+ clearTimeout(v);
621
+ }
622
+ this.healthChecks.clear();
623
+ this.prunedConnectionsCache?.clear();
532
624
 
533
625
  // unregister protocol and handlers
534
626
  if (this._registrarTopologyIds != null) {
@@ -538,14 +630,15 @@ export abstract class DirectStream<
538
630
  }
539
631
 
540
632
  this.queue.clear();
541
- this.helloMap.clear();
542
- this.multiaddrsMap.clear();
543
- this.earlyGoodbyes.clear();
544
633
  this.peers.clear();
545
634
  this.seenCache.clear();
546
635
  this.routes.clear();
547
636
  this.peerKeyHashToPublicKey.clear();
548
- this.peerIdToPublicKey.clear();
637
+
638
+ for (const [k, v] of this._ackCallbacks) {
639
+ clearTimeout(v.timeout);
640
+ }
641
+ this._ackCallbacks.clear();
549
642
  logger.debug("stopped");
550
643
  }
551
644
 
@@ -570,6 +663,13 @@ export abstract class DirectStream<
570
663
  }
571
664
 
572
665
  const publicKey = getPublicKeyFromPeerId(peerId);
666
+
667
+ if (this.prunedConnectionsCache?.has(publicKey.hashcode())) {
668
+ await connection.close();
669
+ await this.components.peerStore.delete(peerId);
670
+ return;
671
+ }
672
+
573
673
  const peer = this.addPeer(
574
674
  peerId,
575
675
  publicKey,
@@ -577,76 +677,30 @@ export abstract class DirectStream<
577
677
  connection.id
578
678
  );
579
679
  const inboundStream = peer.attachInboundStream(stream);
580
- this.processMessages(peerId, inboundStream, peer).catch((err) => {
581
- logger.error(err);
582
- });
680
+ this.processMessages(peer.publicKey, inboundStream, peer).catch(logError);
583
681
  }
584
682
 
585
683
  /**
586
684
  * Registrar notifies an established connection with protocol
587
685
  */
588
- public async onPeerConnected(
589
- peerId: PeerId,
590
- conn: Connection,
591
- properties?: { fromExisting?: boolean }
592
- ) {
593
- if (conn.transient) {
686
+ public async onPeerConnected(peerId: PeerId, connection: Connection) {
687
+ if (
688
+ !this.isStarted() ||
689
+ connection.transient ||
690
+ connection.status !== "open"
691
+ ) {
594
692
  return;
595
693
  }
694
+ const peerKey = getPublicKeyFromPeerId(peerId);
596
695
 
597
- if (!this.isStarted() || conn.status !== "open") {
598
- return;
696
+ if (this.prunedConnectionsCache?.has(peerKey.hashcode())) {
697
+ await connection.close();
698
+ await this.components.peerStore.delete(peerId);
699
+ return; // we recently pruned this connect, dont allow it to connect for a while
599
700
  }
600
701
 
601
702
  try {
602
- // TODO remove/modify when https://github.com/libp2p/js-libp2p/issues/2036 is resolved
603
-
604
- let closeFn: (() => void) | undefined = undefined;
605
- const close = () => {
606
- closeFn?.();
607
- };
608
- this.addEventListener("close", close);
609
- const result = await waitForAsync(
610
- async () => {
611
- try {
612
- const hasProtocol = await this.components.peerStore
613
- .get(peerId)
614
- .then((x) =>
615
- this.multicodecs.find((y) => x.protocols.includes(y))
616
- );
617
- if (!hasProtocol) {
618
- return;
619
- }
620
- } catch (error: any) {
621
- if (error.code === "ERR_NOT_FOUND") {
622
- return;
623
- }
624
- throw error;
625
- }
626
-
627
- return true;
628
- },
629
- {
630
- delayInterval: 100,
631
- timeout: 1e4,
632
- stopperCallback: (cb) => {
633
- closeFn = cb;
634
- }
635
- }
636
- ).finally(() => {
637
- this.removeEventListener("close", close);
638
- });
639
- if (!result) {
640
- return;
641
- }
642
- } catch (error) {
643
- return;
644
- }
645
- try {
646
- const peerKey = getPublicKeyFromPeerId(peerId);
647
- const peerKeyHash = peerKey.hashcode();
648
-
649
- for (const existingStreams of conn.streams) {
703
+ for (const existingStreams of connection.streams) {
650
704
  if (
651
705
  existingStreams.protocol &&
652
706
  this.multicodecs.includes(existingStreams.protocol) &&
@@ -670,29 +724,13 @@ export abstract class DirectStream<
670
724
  }
671
725
 
672
726
  try {
673
- stream = await conn.newStream(this.multicodecs);
727
+ stream = await connection.newStream(this.multicodecs);
674
728
  if (stream.protocol == null) {
675
729
  stream.abort(new Error("Stream was not multiplexed"));
676
730
  return;
677
731
  }
678
- peer = this.addPeer(peerId, peerKey, stream.protocol!, conn.id); // TODO types
732
+ peer = this.addPeer(peerId, peerKey, stream.protocol!, connection.id); // TODO types
679
733
  await peer.attachOutboundStream(stream);
680
-
681
- // TODO do we want to do this?
682
- /* if (!peer.inboundStream) {
683
- const inboundStream = conn.streams.find(
684
- (x) =>
685
- x.stat.protocol &&
686
- this.multicodecs.includes(x.stat.protocol) &&
687
- x.stat.direction === "inbound"
688
- );
689
- if (inboundStream) {
690
- this._onIncomingStream({
691
- connection: conn,
692
- stream: inboundStream,
693
- });
694
- }
695
- } */
696
734
  } catch (error: any) {
697
735
  if (error.code === "ERR_UNSUPPORTED_PROTOCOL") {
698
736
  await delay(100);
@@ -700,7 +738,7 @@ export abstract class DirectStream<
700
738
  }
701
739
 
702
740
  if (
703
- conn.status !== "open" ||
741
+ connection.status !== "open" ||
704
742
  error?.message === "Muxer already closed" ||
705
743
  error.code === "ERR_STREAM_RESET"
706
744
  ) {
@@ -715,110 +753,18 @@ export abstract class DirectStream<
715
753
  return;
716
754
  }
717
755
 
718
- if (properties?.fromExisting) {
719
- return; // we return here because we will enter this method once more once the protocol has been registered for the remote peer
720
- }
721
-
722
- // Add connection with assumed large latency
723
- this.peerIdToPublicKey.set(peerId.toString(), peerKey);
724
- const promises: Promise<any>[] = [];
725
-
726
- /* if (!existingStream) */ {
727
- this.addRouteConnection(
728
- this.publicKey,
729
- peerKey,
730
- Number.MAX_SAFE_INTEGER
731
- );
732
-
733
- // Get accurate latency
734
- promises.push(this.ping(peer));
735
-
736
- // Say hello
737
- promises.push(
738
- this.publishMessage(
739
- this.components.peerId,
740
- await new Hello({
741
- multiaddrs: this.components.addressManager
742
- .getAddresses()
743
- .map((x) => x.toString())
744
- }).sign(this.sign),
745
- [peer]
746
- )
747
- );
748
- // Send my goodbye early if I disconnect for some reason, (so my peer can say goodbye for me)
749
- // TODO add custom condition fn for doing below
750
- promises.push(
751
- this.publishMessage(
752
- this.components.peerId,
753
- await new Goodbye({ early: true }).sign(this.sign),
754
- [peer]
755
- )
756
- );
757
-
758
- // replay all hellos
759
- for (const [sender, hellos] of this.helloMap) {
760
- if (sender === peerKeyHash) {
761
- // Don't say hellos from sender to same sender (uneccessary)
762
- continue;
763
- }
764
-
765
- outer: for (const [key, hello] of hellos) {
766
- if (!hello.header.verify()) {
767
- hellos.delete(key);
768
- }
769
- for (const signer of hello.signatures.publicKeys) {
770
- if (!this.routes.hasNode(signer.hashcode())) {
771
- // purge this hello since it has travelled a path that no longer exist
772
- hellos.delete(key);
773
- continue outer;
774
- }
775
- }
776
-
777
- promises.push(
778
- this.publishMessage(this.components.peerId, hello, [peer])
779
- );
780
- }
781
- }
782
- }
783
- const resolved = await Promise.all(promises);
784
- return resolved;
756
+ this.addRouteConnection(
757
+ this.publicKeyHash,
758
+ peerKey.hashcode(),
759
+ peerKey,
760
+ 0,
761
+ +new Date()
762
+ );
785
763
  } catch (err: any) {
786
764
  logger.error(err);
787
765
  }
788
766
  }
789
767
 
790
- private addRouteConnection(
791
- from: PublicSignKey,
792
- to: PublicSignKey,
793
- latency: number
794
- ) {
795
- this.peerKeyHashToPublicKey.set(from.hashcode(), from);
796
- this.peerKeyHashToPublicKey.set(to.hashcode(), to);
797
- const links = this.routes.addLink(from.hashcode(), to.hashcode(), latency);
798
- for (const added of links) {
799
- const key = this.peerKeyHashToPublicKey.get(added);
800
- if (key?.equals(this.publicKey) === false) {
801
- this.onPeerReachable(key!);
802
- }
803
- }
804
- }
805
-
806
- removeRouteConnection(from: PublicSignKey, to: PublicSignKey) {
807
- const has = this.routes.hasNode(to.hashcode());
808
- if (!has) {
809
- this.onPeerUnreachable(to);
810
- } else {
811
- const links = this.routes.deleteLink(from.hashcode(), to.hashcode());
812
- for (const deleted of links) {
813
- const key = this.peerKeyHashToPublicKey.get(deleted)!;
814
- this.peerKeyHashToPublicKey.delete(deleted);
815
- if (key?.equals(this.publicKey) === false) {
816
- this.onPeerUnreachable(key!);
817
- }
818
- }
819
- }
820
- }
821
-
822
768
  /**
823
769
  * Registrar notifies a closing connection with pubsub protocol
824
770
  */
@@ -826,6 +772,7 @@ export abstract class DirectStream<
826
772
  // PeerId could be me, if so, it means that I am disconnecting
827
773
  const peerKey = getPublicKeyFromPeerId(peerId);
828
774
  const peerKeyHash = peerKey.hashcode();
775
+
829
776
  const connections = this.components.connectionManager
830
777
  .getConnectionsMap()
831
778
  .get(peerId);
@@ -845,22 +792,69 @@ export abstract class DirectStream<
845
792
 
846
793
  if (!this.publicKey.equals(peerKey)) {
847
794
  await this._removePeer(peerKey);
848
- this.removeRouteConnection(this.publicKey, peerKey);
849
795
 
850
796
  // Notify network
851
- const earlyGoodBye = this.earlyGoodbyes.get(peerKeyHash);
852
- if (earlyGoodBye) {
853
- earlyGoodBye.early = false;
854
- await earlyGoodBye.sign(this.sign);
855
- await this.publishMessage(this.components.peerId, earlyGoodBye);
856
- this.earlyGoodbyes.delete(peerKeyHash);
797
+ const dependent = this.routes.getDependent(peerKeyHash);
798
+ this.removeRouteConnection(peerKeyHash, true);
799
+
800
+ if (dependent.length > 0) {
801
+ await this.publishMessage(
802
+ this.publicKey,
803
+ await new Goodbye({
804
+ leaving: [peerKeyHash],
805
+ header: new MessageHeader({
806
+ mode: new SilentDelivery({ to: dependent, redundancy: 2 })
807
+ })
808
+ }).sign(this.sign)
809
+ );
857
810
  }
858
- this.peerIdToPublicKey.delete(peerId.toString());
859
811
  }
860
812
 
861
813
  logger.debug("connection ended:" + peerKey.toString());
862
814
  }
863
815
 
816
+ public removeRouteConnection(hash: string, neigbour: boolean) {
817
+ const unreachable = neigbour
818
+ ? this.routes.removeNeighbour(hash)
819
+ : this.routes.removeTarget(hash);
820
+ for (const node of unreachable) {
821
+ this.onPeerUnreachable(node); // TODO types
822
+ this.peerKeyHashToPublicKey.delete(node);
823
+ }
824
+ }
825
+
826
+ public addRouteConnection(
827
+ from: string,
828
+ neighbour: string,
829
+ target: PublicSignKey,
830
+ distance: number,
831
+ session: number,
832
+ pending = false
833
+ ) {
834
+ const targetHash = typeof target === "string" ? target : target.hashcode();
835
+ const wasReachable =
836
+ from === this.publicKeyHash
837
+ ? this.routes.isReachable(from, targetHash)
838
+ : true;
839
+ if (pending) {
840
+ this.routes.addPendingRouteConnection(session, {
841
+ distance,
842
+ from,
843
+ neighbour,
844
+ target: targetHash
845
+ });
846
+ } else {
847
+ this.routes.add(from, neighbour, targetHash, distance, session);
848
+ }
849
+
850
+ const newPeer =
851
+ wasReachable === false && this.routes.isReachable(from, targetHash);
852
+ if (newPeer) {
853
+ this.peerKeyHashToPublicKey.set(target.hashcode(), target);
854
+ this.onPeerReachable(target); // TODO types
855
+ }
856
+ }
857
+
864
858
  /**
865
859
  * invoked when a new peer becomes reachable
866
860
  * @param publicKeyHash
@@ -876,13 +870,14 @@ export abstract class DirectStream<
876
870
  * invoked when a new peer becomes unreachable
877
871
  * @param publicKeyHash
878
872
  */
879
- public onPeerUnreachable(publicKey: PublicSignKey) {
873
+ public onPeerUnreachable(hash: string) {
880
874
  // override this fn
881
- this.helloMap.delete(publicKey.hashcode());
882
- this.multiaddrsMap.delete(publicKey.hashcode());
883
875
 
884
876
  this.dispatchEvent(
885
- new CustomEvent("peer:unreachable", { detail: publicKey })
877
+ // TODO types
878
+ new CustomEvent("peer:unreachable", {
879
+ detail: this.peerKeyHashToPublicKey.get(hash)!
880
+ })
886
881
  );
887
882
  }
888
883
 
@@ -896,6 +891,13 @@ export abstract class DirectStream<
896
891
  connId: string
897
892
  ): PeerStreams {
898
893
  const publicKeyHash = publicKey.hashcode();
894
+
895
+ const hc = this.healthChecks.get(publicKeyHash);
896
+ if (hc) {
897
+ clearTimeout(hc);
898
+ this.healthChecks.delete(publicKeyHash);
899
+ }
900
+
899
901
  const existing = this.peers.get(publicKeyHash);
900
902
 
901
903
  // If peer streams already exists, do nothing
@@ -920,6 +922,7 @@ export abstract class DirectStream<
920
922
  peerStreams.addEventListener("close", () => this._removePeer(publicKey), {
921
923
  once: true
922
924
  });
925
+
923
926
  return peerStreams;
924
927
  }
925
928
 
@@ -949,44 +952,14 @@ export abstract class DirectStream<
949
952
  * Responsible for processing each RPC message received by other peers.
950
953
  */
951
954
  async processMessages(
952
- peerId: PeerId,
955
+ peerId: PublicSignKey,
953
956
  stream: AsyncIterable<Uint8ArrayList>,
954
957
  peerStreams: PeerStreams
955
958
  ) {
956
959
  try {
957
960
  await pipe(stream, async (source) => {
958
961
  for await (const data of source) {
959
- const msgId = await getMsgId(data);
960
- if (this.seenCache.has(msgId)) {
961
- // we got message that WE sent?
962
-
963
- /**
964
- * Most propobable reason why we arrive here is a race condition/issue
965
-
966
- ┌─┐
967
- │0│
968
- └△┘
969
- ┌▽┐
970
- │1│
971
- └△┘
972
- ┌▽┐
973
- │2│
974
- └─┘
975
-
976
- from 2s perspective,
977
-
978
- if everyone conents to each other at the same time, then 0 will say hello to 1 and 1 will save that hello to resend to 2 if 2 ever connects
979
- but two is already connected by onPeerConnected has not been invoked yet, so the hello message gets forwarded,
980
- and later onPeerConnected gets invoked on 1, and the same message gets resent to 2
981
- */
982
-
983
- continue;
984
- }
985
-
986
- this.seenCache.add(msgId);
987
- this.processRpc(peerId, peerStreams, data).catch((err) =>
988
- logger.warn(err)
989
- );
962
+ this.processRpc(peerId, peerStreams, data).catch(logError);
990
963
  }
991
964
  });
992
965
  } catch (err: any) {
@@ -1004,15 +977,10 @@ export abstract class DirectStream<
1004
977
  * Handles an rpc request from a peer
1005
978
  */
1006
979
  async processRpc(
1007
- from: PeerId,
980
+ from: PublicSignKey,
1008
981
  peerStreams: PeerStreams,
1009
982
  message: Uint8ArrayList
1010
983
  ): Promise<boolean> {
1011
- if (!this.acceptFrom(from)) {
1012
- logger.debug("received message from unacceptable peer %p", from);
1013
- return false;
1014
- }
1015
-
1016
984
  // logger.debug("rpc from " + from + ", " + this.peerIdStr);
1017
985
 
1018
986
  if (message.length > 0) {
@@ -1025,31 +993,36 @@ export abstract class DirectStream<
1025
993
  logger.error(err);
1026
994
  }
1027
995
  })
1028
- .catch((err) => logger.warn(err));
996
+ .catch(logError);
1029
997
  }
1030
998
 
1031
999
  return true;
1032
1000
  }
1033
1001
 
1002
+ private async modifySeenCache(
1003
+ message: Uint8Array,
1004
+ getIdFn: (bytes: Uint8Array) => Promise<string> = getMsgId
1005
+ ) {
1006
+ const msgId = await getIdFn(message);
1007
+ const seen = this.seenCache.get(msgId);
1008
+ this.seenCache.add(msgId, seen ? seen + 1 : 1);
1009
+ return seen || 0;
1010
+ }
1011
+
1034
1012
  /**
1035
1013
  * Handles a message from a peer
1036
1014
  */
1037
1015
  async processMessage(
1038
- from: PeerId,
1016
+ from: PublicSignKey,
1039
1017
  peerStream: PeerStreams,
1040
1018
  msg: Uint8ArrayList
1041
1019
  ) {
1042
- if (!from.publicKey) {
1043
- return;
1044
- }
1045
-
1046
- if (this.components.peerId.equals(from) && !this.emitSelf) {
1020
+ if (!this.started) {
1047
1021
  return;
1048
1022
  }
1049
1023
 
1050
1024
  // Ensure the message is valid before processing it
1051
1025
  const message: Message | undefined = Message.from(msg);
1052
-
1053
1026
  this.dispatchEvent(
1054
1027
  new CustomEvent("message", {
1055
1028
  detail: message
@@ -1057,265 +1030,351 @@ export abstract class DirectStream<
1057
1030
  );
1058
1031
 
1059
1032
  if (message instanceof DataMessage) {
1060
- await this.onDataMessage(from, peerStream, message);
1061
- } else if (message instanceof Hello) {
1062
- await this.onHello(from, peerStream, message);
1063
- } else if (message instanceof Goodbye) {
1064
- await this.onGoodbye(from, peerStream, message);
1065
- } else if (message instanceof PingPong) {
1066
- await this.onPing(from, peerStream, message);
1033
+ // DONT await this since it might introduce a dead-lock
1034
+ this._onDataMessage(from, peerStream, msg, message).catch(logError);
1067
1035
  } else {
1068
- throw new Error("Unsupported");
1036
+ if (message instanceof ACK) {
1037
+ this.onAck(from, peerStream, msg, message).catch(logError);
1038
+ } else if (message instanceof Goodbye) {
1039
+ this.onGoodBye(from, peerStream, msg, message).catch(logError);
1040
+ } else {
1041
+ throw new Error("Unsupported message type");
1042
+ }
1069
1043
  }
1070
1044
  }
1071
1045
 
1072
- async onDataMessage(
1073
- from: PeerId,
1046
+ public shouldIgnore(message: DataMessage, seenBefore: number) {
1047
+ const fromMe = message.header.signatures?.publicKeys.find((x) =>
1048
+ x.equals(this.publicKey)
1049
+ );
1050
+
1051
+ if (fromMe) {
1052
+ return true;
1053
+ }
1054
+
1055
+ if (
1056
+ (seenBefore > 0 &&
1057
+ message.header.mode instanceof SeekDelivery === false) ||
1058
+ (message.header.mode instanceof SeekDelivery &&
1059
+ seenBefore >= message.header.mode.redundancy)
1060
+ ) {
1061
+ return true;
1062
+ }
1063
+
1064
+ return false;
1065
+ }
1066
+
1067
+ public async onDataMessage(
1068
+ from: PublicSignKey,
1074
1069
  peerStream: PeerStreams,
1075
- message: DataMessage
1070
+ message: DataMessage,
1071
+ seenBefore: number
1076
1072
  ) {
1077
- const isFromSelf = this.components.peerId.equals(from);
1078
- if (!isFromSelf || this.emitSelf) {
1079
- const isForAll = message.to.length === 0;
1080
- const isForMe =
1081
- !isForAll && message.to.find((x) => x === this.publicKeyHash);
1082
- if (isForAll || isForMe) {
1083
- if (
1084
- this.signaturePolicy === "StictSign" &&
1085
- !(await message.verify(this.signaturePolicy === "StictSign"))
1086
- ) {
1087
- // we don't verify messages we don't dispatch because of the performance penalty // TODO add opts for this
1088
- logger.warn("Recieved message with invalid signature or timestamp");
1089
- return false;
1090
- }
1073
+ if (this.shouldIgnore(message, seenBefore)) {
1074
+ return false;
1075
+ }
1091
1076
 
1077
+ let isForMe = false;
1078
+ if (message.header.mode instanceof AnyWhere) {
1079
+ isForMe = true;
1080
+ } else {
1081
+ const isFromSelf = this.publicKey.equals(from);
1082
+ if (!isFromSelf) {
1083
+ isForMe =
1084
+ message.header.mode.to == null ||
1085
+ message.header.mode.to.find((x) => x === this.publicKeyHash) != null;
1086
+ }
1087
+ }
1088
+
1089
+ if (isForMe) {
1090
+ if ((await this.maybeVerifyMessage(message)) === false) {
1091
+ // we don't verify messages we don't dispatch because of the performance penalty // TODO add opts for this
1092
+ logger.warn("Recieved message with invalid signature or timestamp");
1093
+ return false;
1094
+ }
1095
+
1096
+ await this.acknowledgeMessage(peerStream, message, seenBefore);
1097
+
1098
+ if (seenBefore === 0 && message.data) {
1092
1099
  this.dispatchEvent(
1093
1100
  new CustomEvent("data", {
1094
1101
  detail: message
1095
1102
  })
1096
1103
  );
1097
1104
  }
1098
- if (isForMe && message.to.length === 1) {
1105
+ }
1106
+
1107
+ if (
1108
+ message.header.mode instanceof SilentDelivery ||
1109
+ message.header.mode instanceof AcknowledgeDelivery
1110
+ ) {
1111
+ if (
1112
+ message.header.mode.to &&
1113
+ message.header.mode.to.length === 1 &&
1114
+ message.header.mode.to[0] === this.publicKeyHash
1115
+ ) {
1099
1116
  // dont forward this message anymore because it was meant ONLY for me
1100
1117
  return true;
1101
1118
  }
1102
1119
  }
1103
1120
 
1104
1121
  // Forward
1105
- await this.relayMessage(from, message);
1106
- return true;
1107
- }
1108
-
1109
- async onHello(from: PeerId, peerStream: PeerStreams, message: Hello) {
1110
- if (!(await message.verify(false))) {
1111
- const a = message.header.verify();
1112
- const b =
1113
- message.networkInfo.pingLatencies.length ===
1114
- message.signatures.signatures.length - 1;
1115
- logger.warn(
1116
- `Recieved hello message that did not verify. Header: ${a}, Ping info ${b}, Signatures ${
1117
- a && b
1118
- }`
1119
- );
1120
- return false;
1122
+ if (message.header.mode instanceof SeekDelivery || seenBefore === 0) {
1123
+ // DONT await this since it might introduce a dead-lock
1124
+ if (message.header.mode instanceof SeekDelivery) {
1125
+ if (seenBefore < message.header.mode.redundancy) {
1126
+ const to = [...this.peers.values()].filter(
1127
+ (x) =>
1128
+ !message.header.signatures?.publicKeys.find((y) =>
1129
+ y.equals(x.publicKey)
1130
+ ) && x != peerStream
1131
+ );
1132
+ if (to.length > 0) {
1133
+ this.relayMessage(from, message, to);
1134
+ }
1135
+ }
1136
+ } else {
1137
+ this.relayMessage(from, message);
1138
+ }
1121
1139
  }
1140
+ }
1122
1141
 
1123
- const sender = message.sender?.hashcode();
1124
- if (!sender) {
1125
- logger.warn("Recieved hello without sender");
1126
- return false;
1127
- }
1142
+ public async maybeVerifyMessage(message: DataMessage) {
1143
+ return message.verify(this.signaturePolicy === "StictSign");
1144
+ }
1128
1145
 
1129
- const signatures = message.signatures;
1130
- for (let i = 0; i < signatures.signatures.length - 1; i++) {
1131
- this.addRouteConnection(
1132
- signatures.signatures[i].publicKey,
1133
- signatures.signatures[i + 1].publicKey,
1134
- message.networkInfo.pingLatencies[i]
1146
+ async acknowledgeMessage(
1147
+ peerStream: PeerStreams,
1148
+ message: DataMessage,
1149
+ seenBefore: number
1150
+ ) {
1151
+ if (
1152
+ (message.header.mode instanceof SeekDelivery ||
1153
+ message.header.mode instanceof AcknowledgeDelivery) &&
1154
+ seenBefore < message.header.mode.redundancy
1155
+ ) {
1156
+ await this.publishMessage(
1157
+ this.publicKey,
1158
+ await new ACK({
1159
+ messageIdToAcknowledge: message.id,
1160
+ seenCounter: seenBefore,
1161
+
1162
+ // TODO only give origin info to peers we want to connect to us
1163
+ header: new MessageHeader({
1164
+ mode: new TracedDelivery(
1165
+ message.header.signatures!.publicKeys.map((x) => x.hashcode())
1166
+ ),
1167
+
1168
+ // include our origin if message is SeekDelivery and we have not recently pruned a connection to this peer
1169
+ origin:
1170
+ message.header.mode instanceof SeekDelivery &&
1171
+ !message.header.signatures!.publicKeys.find(
1172
+ (x) => this.prunedConnectionsCache?.has(x.hashcode())
1173
+ )
1174
+ ? new MultiAddrinfo(
1175
+ this.components.addressManager
1176
+ .getAddresses()
1177
+ .map((x) => x.toString())
1178
+ )
1179
+ : undefined
1180
+ })
1181
+ }).sign(this.sign),
1182
+ [peerStream]
1135
1183
  );
1136
1184
  }
1185
+ }
1137
1186
 
1138
- message.networkInfo.pingLatencies.push(
1139
- peerStream.pingLatency || 4294967295
1140
- ); // TODO don't propagate if latency is high?
1141
-
1142
- await message.sign(this.sign); // sign it so othere peers can now I have seen it (and can build a network graph from trace info)
1143
-
1144
- let hellos = this.helloMap.get(sender);
1145
- if (!hellos) {
1146
- hellos = new Map();
1147
- this.helloMap.set(sender, hellos);
1148
- }
1149
-
1150
- this.multiaddrsMap.set(sender, message.multiaddrs);
1151
-
1152
- const helloSignaturHash = await message.signatures.hashPublicKeys();
1153
- const existingHello = hellos.get(helloSignaturHash);
1154
- if (existingHello) {
1155
- if (existingHello.header.expires < message.header.expires) {
1156
- hellos.set(helloSignaturHash, message);
1157
- }
1158
- } else {
1159
- hellos.set(helloSignaturHash, message);
1160
- }
1187
+ private async _onDataMessage(
1188
+ from: PublicSignKey,
1189
+ peerStream: PeerStreams,
1190
+ messageBytes: Uint8ArrayList | Uint8Array,
1191
+ message: DataMessage
1192
+ ) {
1193
+ const seenBefore = await this.modifySeenCache(
1194
+ messageBytes instanceof Uint8ArrayList
1195
+ ? messageBytes.subarray()
1196
+ : messageBytes
1197
+ );
1161
1198
 
1162
- // Forward
1163
- await this.relayMessage(from, message);
1164
- return true;
1199
+ return this.onDataMessage(from, peerStream, message, seenBefore);
1165
1200
  }
1166
1201
 
1167
- async onGoodbye(from: PeerId, peerStream: PeerStreams, message: Goodbye) {
1168
- if (!(await message.verify(false))) {
1169
- logger.warn("Recieved message with invalid signature or timestamp");
1202
+ async onAck(
1203
+ publicKey: PublicSignKey,
1204
+ peerStream: PeerStreams,
1205
+ messageBytes: Uint8ArrayList | Uint8Array,
1206
+ message: ACK
1207
+ ) {
1208
+ const seenBefore = await this.modifySeenCache(
1209
+ messageBytes instanceof Uint8Array
1210
+ ? messageBytes
1211
+ : messageBytes.subarray(),
1212
+ (bytes) => sha256Base64(bytes)
1213
+ );
1214
+
1215
+ if (seenBefore > 1) {
1216
+ logger.debug(
1217
+ "Received message already seen of type: " + message.constructor.name
1218
+ );
1170
1219
  return false;
1171
1220
  }
1172
1221
 
1173
- const sender = message.sender?.hashcode();
1174
- if (!sender) {
1175
- logger.warn("Recieved hello without sender");
1222
+ if (!(await message.verify(true))) {
1223
+ logger.warn(`Recieved ACK message that did not verify`);
1176
1224
  return false;
1177
1225
  }
1178
1226
 
1179
- const peerKey = getPublicKeyFromPeerId(from);
1180
- const peerKeyHash = peerKey.hashcode();
1181
- if (message.early) {
1182
- this.earlyGoodbyes.set(peerKeyHash, message);
1183
- } else {
1184
- const signatures = message.signatures;
1185
- /* TODO Should we update routes on goodbye?
1186
- for (let i = 1; i < signatures.signatures.length - 1; i++) {
1187
- this.addRouteConnection(
1188
- signatures.signatures[i].publicKey,
1189
- signatures.signatures[i + 1].publicKey
1190
- );
1191
- }
1192
- */
1193
-
1194
- //let neighbour = message.trace[1] || this.peerIdStr;
1195
- this.removeRouteConnection(
1196
- signatures.signatures[0].publicKey,
1197
- signatures.signatures[1].publicKey || this.publicKey
1198
- );
1227
+ const messageIdString = toBase64(message.messageIdToAcknowledge);
1228
+ const myIndex = message.header.mode.trace.findIndex(
1229
+ (x) => x === this.publicKeyHash
1230
+ );
1231
+ const next = message.header.mode.trace[myIndex - 1];
1232
+ const nextStream = next ? this.peers.get(next) : undefined;
1199
1233
 
1200
- const relayToPeers: PeerStreams[] = [];
1201
- for (const stream of this.peers.values()) {
1202
- if (stream.peerId.equals(from)) {
1203
- continue;
1204
- }
1205
- relayToPeers.push(stream);
1206
- }
1207
- await message.sign(this.sign); // sign it so othere peers can now I have seen it (and can build a network graph from trace info)
1234
+ this._ackCallbacks
1235
+ .get(messageIdString)
1236
+ ?.callback(message, peerStream, nextStream);
1208
1237
 
1209
- const hellos = this.helloMap.get(sender);
1210
- if (hellos) {
1211
- const helloSignaturHash = await message.signatures.hashPublicKeys();
1212
- hellos.delete(helloSignaturHash);
1238
+ // relay ACK ?
1239
+ // send exactly backwards same route we got this message
1240
+ if (nextStream) {
1241
+ await this.publishMessage(this.publicKey, message, [nextStream], true);
1242
+ } else {
1243
+ // if origin exist (we can connect to remote peer) && we have autodialer turned on
1244
+ if (message.header.origin && this.connectionManagerOptions.dialer) {
1245
+ this.maybeConnectDirectly(
1246
+ message.header.signatures!.publicKeys[0].hashcode(),
1247
+ message.header.origin
1248
+ );
1213
1249
  }
1214
-
1215
- // Forward
1216
- await this.relayMessage(from, message);
1217
1250
  }
1218
- return true;
1219
1251
  }
1220
1252
 
1221
- async onPing(from: PeerId, peerStream: PeerStreams, message: PingPong) {
1222
- if (message instanceof Ping) {
1223
- // respond with pong
1224
- await this.publishMessage(
1225
- this.components.peerId,
1226
- new Pong(message.pingBytes),
1227
- [peerStream]
1253
+ async onGoodBye(
1254
+ publicKey: PublicSignKey,
1255
+ peerStream: PeerStreams,
1256
+ messageBytes: Uint8ArrayList | Uint8Array,
1257
+ message: Goodbye
1258
+ ) {
1259
+ const seenBefore = await this.modifySeenCache(
1260
+ messageBytes instanceof Uint8Array
1261
+ ? messageBytes
1262
+ : messageBytes.subarray()
1263
+ );
1264
+
1265
+ if (seenBefore > 0) {
1266
+ logger.debug(
1267
+ "Received message already seen of type: " + message.constructor.name
1228
1268
  );
1229
- } else if (message instanceof Pong) {
1230
- // Let the (waiting) thread know that we have received the pong
1231
- peerStream.pingJob?.resolve();
1232
- } else {
1233
- throw new Error("Unsupported");
1269
+ return;
1234
1270
  }
1235
- }
1236
1271
 
1237
- async ping(stream: PeerStreams): Promise<number | undefined> {
1238
- return new Promise<number | undefined>((resolve, reject) => {
1239
- stream.pingJob?.abort();
1240
- const ping = new Ping();
1241
- const start = +new Date();
1242
- const timeout = setTimeout(() => {
1243
- reject(new TimeoutError("Ping timed out"));
1244
- }, 10000);
1245
- const resolver = () => {
1246
- const end = +new Date();
1247
- clearTimeout(timeout);
1248
-
1249
- // TODO what happens if a peer send a ping back then leaves? Any problems?
1250
- const latency = end - start;
1251
- stream.pingLatency = latency;
1252
- this.addRouteConnection(this.publicKey, stream.publicKey, latency);
1253
- resolve(undefined);
1254
- };
1255
- stream.pingJob = {
1256
- resolve: resolver,
1257
- abort: () => {
1258
- clearTimeout(timeout);
1259
- resolve(undefined);
1260
- }
1261
- };
1262
- this.publishMessage(this.components.peerId, ping, [stream]).catch(
1263
- (err) => {
1264
- clearTimeout(timeout);
1265
- reject(err);
1272
+ if (!(await message.verify(true))) {
1273
+ logger.warn(`Recieved ACK message that did not verify`);
1274
+ return false;
1275
+ }
1276
+
1277
+ const filteredLeaving = message.leaving.filter((x) =>
1278
+ this.routes.hasTarget(x)
1279
+ );
1280
+
1281
+ if (filteredLeaving.length > 0) {
1282
+ this.publish(new Uint8Array(0), {
1283
+ mode: new SeekDelivery({
1284
+ to: filteredLeaving,
1285
+ redundancy: DEFAULT_SEEK_MESSAGE_REDUDANCY
1286
+ })
1287
+ }).catch((e) => {
1288
+ if (e instanceof TimeoutError || e instanceof AbortError) {
1289
+ // peer left or closed
1290
+ } else {
1291
+ throw e;
1266
1292
  }
1293
+ }); // this will remove the target if it is still not reable
1294
+ }
1295
+
1296
+ for (const target of message.leaving) {
1297
+ // relay message to every one who previously talked to this peer
1298
+ const dependent = this.routes.getDependent(target);
1299
+ message.header.mode.to = [...message.header.mode.to, ...dependent];
1300
+ message.header.mode.to = message.header.mode.to.filter(
1301
+ (x) => x !== this.publicKeyHash
1267
1302
  );
1268
- });
1269
- }
1270
1303
 
1271
- /**
1272
- * Whether to accept a message from a peer
1273
- * Override to create a graylist
1274
- */
1275
- acceptFrom(id: PeerId) {
1276
- return true;
1304
+ if (message.header.mode.to.length > 0) {
1305
+ await this.publishMessage(publicKey, message, undefined, true);
1306
+ }
1307
+ }
1277
1308
  }
1278
1309
 
1279
1310
  async createMessage(
1280
- data: Uint8Array | Uint8ArrayList,
1281
- options?: { to?: (string | PublicSignKey | PeerId)[] | Set<string> }
1311
+ data: Uint8Array | Uint8ArrayList | undefined,
1312
+ options: WithTo | WithMode
1282
1313
  ) {
1283
1314
  // dispatch the event if we are interested
1284
- let toHashes: string[];
1285
- if (options?.to) {
1286
- if (options.to instanceof Set) {
1287
- toHashes = new Array(options.to.size);
1288
- } else {
1289
- toHashes = new Array(options.to.length);
1315
+
1316
+ let mode: SilentDelivery | SeekDelivery | AcknowledgeDelivery | AnyWhere = (
1317
+ options as WithMode
1318
+ ).mode
1319
+ ? (options as WithMode).mode!
1320
+ : new SilentDelivery({
1321
+ to: (options as WithTo).to!,
1322
+ redundancy: DEFAULT_SILENT_MESSAGE_REDUDANCY
1323
+ });
1324
+
1325
+ if (
1326
+ mode instanceof AcknowledgeDelivery ||
1327
+ mode instanceof SilentDelivery ||
1328
+ mode instanceof SeekDelivery
1329
+ ) {
1330
+ if (mode.to?.find((x) => x === this.publicKeyHash)) {
1331
+ mode.to = mode.to.filter((x) => x !== this.publicKeyHash);
1290
1332
  }
1333
+ }
1291
1334
 
1292
- let i = 0;
1293
- for (const to of options.to) {
1294
- toHashes[i++] =
1295
- to instanceof PublicSignKey
1296
- ? to.hashcode()
1297
- : typeof to === "string"
1298
- ? to
1299
- : getPublicKeyFromPeerId(to).hashcode();
1335
+ if (mode instanceof AcknowledgeDelivery || mode instanceof SilentDelivery) {
1336
+ const now = +new Date();
1337
+ for (const hash of mode.to) {
1338
+ const neighbourRoutes = this.routes.routes
1339
+ .get(this.publicKeyHash)
1340
+ ?.get(hash);
1341
+ if (
1342
+ !neighbourRoutes ||
1343
+ now - neighbourRoutes.session >
1344
+ neighbourRoutes.list.length * this.routeSeekInterval
1345
+ ) {
1346
+ mode = new SeekDelivery({
1347
+ to: mode.to,
1348
+ redundancy: DEFAULT_SEEK_MESSAGE_REDUDANCY
1349
+ });
1350
+ break;
1351
+ }
1300
1352
  }
1301
- } else {
1302
- toHashes = [];
1303
1353
  }
1354
+
1304
1355
  const message = new DataMessage({
1305
1356
  data: data instanceof Uint8ArrayList ? data.subarray() : data,
1306
- to: toHashes
1357
+ header: new MessageHeader({ mode })
1307
1358
  });
1308
- if (this.signaturePolicy === "StictSign") {
1359
+
1360
+ if (
1361
+ this.signaturePolicy === "StictSign" ||
1362
+ mode instanceof SeekDelivery ||
1363
+ mode instanceof AcknowledgeDelivery
1364
+ ) {
1309
1365
  await message.sign(this.sign);
1310
1366
  }
1367
+
1311
1368
  return message;
1312
1369
  }
1313
1370
  /**
1314
1371
  * Publishes messages to all peers
1315
1372
  */
1316
1373
  async publish(
1317
- data: Uint8Array | Uint8ArrayList,
1318
- options?: { to?: (string | PublicSignKey | PeerId)[] | Set<string> }
1374
+ data: Uint8Array | Uint8ArrayList | undefined,
1375
+ options: WithMode | WithTo = {
1376
+ mode: new SeekDelivery({ redundancy: DEFAULT_SEEK_MESSAGE_REDUDANCY })
1377
+ }
1319
1378
  ): Promise<Uint8Array> {
1320
1379
  if (!this.started) {
1321
1380
  throw new Error("Not started");
@@ -1323,274 +1382,361 @@ export abstract class DirectStream<
1323
1382
 
1324
1383
  const message = await this.createMessage(data, options);
1325
1384
 
1326
- if (this.emitSelf) {
1327
- super.dispatchEvent(
1328
- new CustomEvent("data", {
1329
- detail: message
1330
- })
1331
- );
1332
- }
1333
-
1334
- // send to all the other peers
1335
- await this.publishMessage(this.components.peerId, message, undefined);
1385
+ await this.publishMessage(this.publicKey, message, undefined);
1336
1386
  return message.id;
1337
1387
  }
1338
1388
 
1339
- public async hello(data?: Uint8Array): Promise<void> {
1340
- if (!this.started) {
1341
- return;
1342
- }
1343
-
1344
- // send to all the other peers
1345
- await this.publishMessage(
1346
- this.components.peerId,
1347
- await new Hello({
1348
- multiaddrs: this.components.addressManager
1349
- .getAddresses()
1350
- .map((x) => x.toString()),
1351
- data
1352
- }).sign(this.sign.bind(this))
1353
- );
1354
- }
1355
-
1356
1389
  public async relayMessage(
1357
- from: PeerId,
1390
+ from: PublicSignKey,
1358
1391
  message: Message,
1359
- to?: PeerStreams[] | PeerMap<PeerStreams>
1392
+ to?: PeerStreams[] | Map<string, PeerStreams>
1360
1393
  ) {
1361
1394
  if (this.canRelayMessage) {
1395
+ if (message instanceof DataMessage) {
1396
+ if (
1397
+ message.header.mode instanceof AcknowledgeDelivery ||
1398
+ message.header.mode instanceof SeekDelivery
1399
+ ) {
1400
+ await message.sign(this.sign);
1401
+ }
1402
+ }
1403
+
1362
1404
  return this.publishMessage(from, message, to, true);
1363
1405
  } else {
1364
- logger.debug("received message we didn't subscribe to. Dropping.");
1406
+ logger.debug("Received a message to relay but canRelayMessage is false");
1365
1407
  }
1366
1408
  }
1367
- public async publishMessage(
1368
- from: PeerId,
1369
- message: Message,
1370
- to?: PeerStreams[] | PeerMap<PeerStreams>,
1409
+
1410
+ private async createDeliveryPromise(
1411
+ from: PublicSignKey,
1412
+ message: DataMessage,
1371
1413
  relayed?: boolean
1372
- ): Promise<void> {
1373
- if (message instanceof DataMessage && !to) {
1374
- // message.to can be distant peers, but "to" are neighbours
1375
- const fanoutMap = new Map<string, string[]>();
1376
-
1377
- // Message to > 0
1378
- if (message.to.length > 0) {
1379
- const missingPathsFor: string[] = [];
1380
- for (const to of message.to) {
1381
- const fromKey = this.peerIdToPublicKey
1382
- .get(from.toString())
1383
- ?.hashcode();
1384
- if (to === this.publicKeyHash || fromKey === to) {
1385
- continue; // don't send to me or backwards
1386
- }
1414
+ ): Promise<{ promise: Promise<void> }> {
1415
+ if (message.header.mode instanceof AnyWhere) {
1416
+ return { promise: Promise.resolve() };
1417
+ }
1418
+ const idString = toBase64(message.id);
1387
1419
 
1388
- const directStream = this.peers.get(to);
1389
- if (directStream) {
1390
- // always favor direct stream, even path seems longer
1391
- const fanout = fanoutMap.get(to);
1392
- if (!fanout) {
1393
- fanoutMap.set(to, [to]);
1394
- } else {
1395
- fanout.push(to);
1396
- }
1397
- continue;
1398
- } else {
1399
- const fromMe = from.equals(this.components.peerId);
1400
- const block = !fromMe ? fromKey : undefined;
1401
- const path = this.routes.getPath(this.publicKeyHash, to, {
1402
- block // prevent send message backwards
1403
- });
1404
-
1405
- if (path && path.length > 0) {
1406
- const fanout = fanoutMap.get(path[1]);
1407
- if (!fanout) {
1408
- fanoutMap.set(path[1], [to]);
1409
- if (
1410
- this.connectionManagerOptions.autoDial &&
1411
- path.length >= 3
1412
- ) {
1413
- // Dont await this even if it is async since this method can fail
1414
- // and might take some time to run
1415
- this.maybeConnectDirectly(path).catch((e) => {
1416
- logger.error(
1417
- "Failed to request direct connection: " + e.message
1418
- );
1419
- });
1420
- }
1421
- continue;
1422
- } else {
1423
- fanout.push(to);
1424
- continue;
1420
+ const existing = this._ackCallbacks.get(idString);
1421
+ if (existing) {
1422
+ return { promise: existing.promise };
1423
+ }
1424
+
1425
+ const deliveryDeferredPromise = pDefer<void>();
1426
+ const fastestNodesReached = new Map<string, number[]>();
1427
+ const messageToSet: Set<string> = new Set();
1428
+ if (message.header.mode.to) {
1429
+ for (const to of message.header.mode.to) {
1430
+ if (to === from.hashcode()) {
1431
+ continue;
1432
+ }
1433
+ messageToSet.add(to);
1434
+
1435
+ if (!relayed && !this.healthChecks.has(to)) {
1436
+ this.healthChecks.set(
1437
+ to,
1438
+ setTimeout(() => {
1439
+ this.removeRouteConnection(to, false);
1440
+ }, this.seekTimeout + 5000)
1441
+ );
1442
+ }
1443
+ }
1444
+ }
1445
+
1446
+ if (messageToSet.size === 0) {
1447
+ deliveryDeferredPromise.resolve(); // we dont know how many answer to expect, just resolve immediately
1448
+ }
1449
+
1450
+ const willGetAllAcknowledgements = !relayed; // Only the origin will get all acks
1451
+
1452
+ // Expected to receive at least 'filterMessageForSeenCounter' acknowledgements from each peer
1453
+ const filterMessageForSeenCounter = relayed
1454
+ ? undefined
1455
+ : message.header.mode instanceof SeekDelivery
1456
+ ? Math.min(this.peers.size, message.header.mode.redundancy)
1457
+ : 1; /* message.deliveryMode instanceof SeekDelivery ? Math.min(this.peers.size - (relayed ? 1 : 0), message.deliveryMode.redundancy) : 1 */
1458
+
1459
+ const finalize = () => {
1460
+ this._ackCallbacks.delete(idString);
1461
+ if (message.header.mode instanceof SeekDelivery) {
1462
+ this.routes.commitPendingRouteConnection(session);
1463
+ }
1464
+ };
1465
+
1466
+ const uniqueAcks = new Set();
1467
+ const session = +new Date();
1468
+ const timeout = setTimeout(async () => {
1469
+ let hasAll = true;
1470
+ finalize();
1471
+
1472
+ // peer not reachable (?)!
1473
+ for (const to of messageToSet) {
1474
+ let foundNode = false;
1475
+
1476
+ if (fastestNodesReached.has(to)) {
1477
+ foundNode = true;
1478
+ break;
1479
+ }
1480
+
1481
+ if (!foundNode && !relayed) {
1482
+ hasAll = false;
1483
+ }
1484
+ }
1485
+
1486
+ if (!hasAll && willGetAllAcknowledgements) {
1487
+ deliveryDeferredPromise.reject(
1488
+ new TimeoutError(
1489
+ `${
1490
+ this.publicKeyHash
1491
+ } Failed to get message ${idString} ${filterMessageForSeenCounter} ${[
1492
+ ...messageToSet
1493
+ ]} delivery acknowledges from all nodes (${
1494
+ fastestNodesReached.size
1495
+ }/${messageToSet.size}). Mode: ${
1496
+ message.header.mode.constructor.name
1497
+ }`
1498
+ )
1499
+ );
1500
+ } else {
1501
+ deliveryDeferredPromise.resolve();
1502
+ }
1503
+ }, this.seekTimeout);
1504
+
1505
+ this._ackCallbacks.set(idString, {
1506
+ promise: deliveryDeferredPromise.promise,
1507
+ callback: (ack: ACK, messageThrough, messageFrom) => {
1508
+ const messageTarget = ack.header.signatures!.publicKeys[0];
1509
+ const messageTargetHash = messageTarget.hashcode();
1510
+ const seenCounter = ack.seenCounter;
1511
+
1512
+ // remove the automatic removal of route timeout since we have observed lifesigns of a peer
1513
+ const timer = this.healthChecks.get(messageTargetHash);
1514
+ clearTimeout(timer);
1515
+ this.healthChecks.delete(messageTargetHash);
1516
+
1517
+ // if the target is not inside the original message to, we still ad the target to our routes
1518
+ // this because a relay might modify the 'to' list and we might receive more answers than initially set
1519
+ if (message.header.mode instanceof SeekDelivery) {
1520
+ this.addRouteConnection(
1521
+ messageFrom?.publicKey.hashcode() || this.publicKeyHash,
1522
+ messageThrough.publicKey.hashcode(),
1523
+ messageTarget,
1524
+ seenCounter,
1525
+ session,
1526
+ true
1527
+ ); // we assume the seenCounter = distance. The more the message has been seen by the target the longer the path is to the target
1528
+ }
1529
+
1530
+ if (messageToSet.has(messageTargetHash)) {
1531
+ // Only keep track of relevant acks
1532
+ if (
1533
+ filterMessageForSeenCounter == null ||
1534
+ seenCounter < filterMessageForSeenCounter
1535
+ ) {
1536
+ // TODO set limit correctly
1537
+ if (seenCounter < MAX_ROUTE_DISTANCE) {
1538
+ let arr = fastestNodesReached.get(messageTargetHash);
1539
+ if (!arr) {
1540
+ arr = [];
1541
+ fastestNodesReached.set(messageTargetHash, arr);
1425
1542
  }
1426
- } else {
1427
- missingPathsFor.push(to);
1543
+ arr.push(seenCounter);
1544
+
1545
+ uniqueAcks.add(messageTargetHash + seenCounter);
1428
1546
  }
1429
1547
  }
1548
+ }
1430
1549
 
1431
- // we can't find path, send message to all peers
1432
- fanoutMap.clear();
1433
- break;
1550
+ // This if clause should never enter for relayed connections, since we don't
1551
+ // know how many ACKs we will get
1552
+ if (
1553
+ filterMessageForSeenCounter != null &&
1554
+ uniqueAcks.size >= messageToSet.size * filterMessageForSeenCounter
1555
+ ) {
1556
+ if (messageToSet.size > 0) {
1557
+ // this statement exist beacuse if we do SEEK and have to = [], then it means we try to reach as many as possible hence we never want to delete this ACK callback
1558
+ // only remove callback function if we actually expected a expected amount of responses
1559
+ clearTimeout(timeout);
1560
+ finalize();
1561
+ }
1562
+ deliveryDeferredPromise.resolve();
1434
1563
  }
1564
+ },
1565
+ timeout
1566
+ });
1567
+ return deliveryDeferredPromise;
1568
+ }
1435
1569
 
1436
- // update to's
1437
- let sentOnce = false;
1438
- if (missingPathsFor.length === 0) {
1439
- if (fanoutMap.size > 0) {
1440
- for (const [neighbour, distantPeers] of fanoutMap) {
1441
- message.to = distantPeers;
1442
- const bytes = message.bytes();
1443
- if (!sentOnce) {
1444
- // if relayed = true, we have already added it to seenCache
1445
- if (!relayed) {
1446
- this.seenCache.add(await getMsgId(bytes));
1447
- }
1448
- sentOnce = true;
1449
- }
1570
+ public async publishMessage(
1571
+ from: PublicSignKey,
1572
+ message: Message,
1573
+ to?: PeerStreams[] | Map<string, PeerStreams>,
1574
+ relayed?: boolean
1575
+ ): Promise<void> {
1576
+ let delivereyPromise: Promise<void> | undefined = undefined as any;
1577
+
1578
+ if (
1579
+ (!message.header.signatures ||
1580
+ message.header.signatures.publicKeys.length === 0) &&
1581
+ message instanceof DataMessage &&
1582
+ message.header.mode instanceof SilentDelivery === false
1583
+ ) {
1584
+ throw new Error("Missing signature");
1585
+ }
1586
+
1587
+ /**
1588
+ * Logic for handling acknowledge messages when we receive them (later)
1589
+ */
1590
+
1591
+ if (
1592
+ message instanceof DataMessage &&
1593
+ message.header.mode instanceof SeekDelivery &&
1594
+ !relayed
1595
+ ) {
1596
+ to = this.peers; // seek delivery will not work unless we try all possible paths
1597
+ }
1598
+
1599
+ if (message instanceof AcknowledgeDelivery) {
1600
+ to = undefined;
1601
+ }
1602
+
1603
+ if (
1604
+ message instanceof DataMessage &&
1605
+ (message.header.mode instanceof SeekDelivery ||
1606
+ message.header.mode instanceof AcknowledgeDelivery)
1607
+ ) {
1608
+ delivereyPromise = (
1609
+ await this.createDeliveryPromise(from, message, relayed)
1610
+ ).promise;
1611
+ }
1612
+
1613
+ const bytes = message.bytes();
1614
+
1615
+ if (!relayed) {
1616
+ const bytesArray = bytes instanceof Uint8Array ? bytes : bytes.subarray();
1617
+ await this.modifySeenCache(bytesArray);
1618
+ }
1619
+
1620
+ /**
1621
+ * For non SEEKing message delivery modes, use routing
1622
+ */
1623
+
1624
+ if (message instanceof DataMessage) {
1625
+ if (
1626
+ (message.header.mode instanceof AcknowledgeDelivery ||
1627
+ message.header.mode instanceof SilentDelivery) &&
1628
+ !to &&
1629
+ message.header.mode.to
1630
+ ) {
1631
+ const fanout = this.routes.getFanout(
1632
+ from,
1633
+ message.header.mode.to,
1634
+ message.header.mode.redundancy
1635
+ );
1450
1636
 
1637
+ if (fanout) {
1638
+ if (fanout.size > 0) {
1639
+ for (const [neighbour, _distantPeers] of fanout) {
1451
1640
  const stream = this.peers.get(neighbour);
1452
1641
  stream?.waitForWrite(bytes).catch((e) => {
1453
1642
  logger.error("Failed to publish message: " + e.message);
1454
1643
  });
1455
1644
  }
1456
- return; // we are done sending the message in all direction with updates 'to' lists
1645
+ return delivereyPromise; // we are done sending the message in all direction with updates 'to' lists
1457
1646
  }
1458
- return;
1647
+
1648
+ return; // we defintely that we should not forward the message anywhere
1459
1649
  }
1460
1650
 
1651
+ return;
1652
+
1461
1653
  // else send to all (fallthrough to code below)
1462
1654
  }
1463
1655
  }
1464
1656
 
1465
1657
  // We fils to send the message directly, instead fallback to floodsub
1466
- const peers: PeerStreams[] | PeerMap<PeerStreams> = to || this.peers;
1658
+ const peers: PeerStreams[] | Map<string, PeerStreams> = to || this.peers;
1467
1659
  if (
1468
1660
  peers == null ||
1469
1661
  (Array.isArray(peers) && peers.length === 0) ||
1470
1662
  (peers instanceof Map && peers.size === 0)
1471
1663
  ) {
1472
- logger.debug("no peers are subscribed");
1664
+ logger.debug("No peers to send to");
1473
1665
  return;
1474
1666
  }
1475
1667
 
1476
- const bytes = message.bytes();
1477
1668
  let sentOnce = false;
1478
1669
  for (const stream of peers.values()) {
1479
1670
  const id = stream as PeerStreams;
1480
- if (id.peerId.equals(from)) {
1671
+ if (id.publicKey.equals(from)) {
1481
1672
  continue;
1482
1673
  }
1483
1674
 
1484
- if (!sentOnce) {
1485
- sentOnce = true;
1486
- if (!relayed) {
1487
- // if relayed = true, we have already added it to seenCache
1488
- const msgId = await getMsgId(bytes);
1489
- this.seenCache.add(msgId);
1490
- }
1491
- }
1675
+ sentOnce = true;
1676
+
1492
1677
  id.waitForWrite(bytes).catch((e) => {
1493
1678
  logger.error("Failed to publish message: " + e.message);
1494
1679
  });
1495
1680
  }
1496
1681
 
1497
- if (!sentOnce && !relayed) {
1498
- throw new Error("Message did not have any valid receivers. ");
1682
+ if (!sentOnce) {
1683
+ if (!relayed) {
1684
+ throw new Error("Message did not have any valid receivers");
1685
+ }
1499
1686
  }
1687
+ return delivereyPromise;
1500
1688
  }
1501
1689
 
1502
- async maybeConnectDirectly(path: string[]) {
1503
- if (path.length < 3) {
1504
- return;
1505
- }
1506
-
1507
- const toHash = path[path.length - 1];
1508
-
1509
- if (this.peers.has(toHash)) {
1690
+ async maybeConnectDirectly(toHash: string, origin: MultiAddrinfo) {
1691
+ if (this.peers.has(toHash) || this.prunedConnectionsCache?.has(toHash)) {
1510
1692
  return; // TODO, is this expected, or are we to dial more addresses?
1511
1693
  }
1512
1694
 
1513
- // Try to either connect directly
1514
- if (!this.recentDials.has(toHash)) {
1515
- this.recentDials.add(toHash);
1516
- const addrs = this.multiaddrsMap.get(toHash);
1695
+ const addresses = origin.multiaddrs
1696
+ .filter((x) => {
1697
+ const ret = !this.recentDials!.has(x);
1698
+ this.recentDials!.add(x);
1699
+ return ret;
1700
+ })
1701
+ .map((x) => multiaddr(x));
1702
+ if (addresses.length > 0) {
1517
1703
  try {
1518
- if (addrs && addrs.length > 0) {
1519
- await this.components.connectionManager.openConnection(
1520
- addrs.map((x) => multiaddr(x))
1521
- );
1522
- return;
1523
- }
1524
- } catch (error) {
1525
- // continue regardless of error
1526
- }
1527
- }
1528
-
1529
- // Connect through a closer relay that maybe does holepunch for us
1530
- const nextToHash = path[path.length - 2];
1531
- const routeKey = nextToHash + toHash;
1532
- if (!this.recentDials.has(routeKey)) {
1533
- this.recentDials.add(routeKey);
1534
- const to = this.peerKeyHashToPublicKey.get(toHash)! as Ed25519PublicKey;
1535
- const toPeerId = await to.toPeerId();
1536
- const addrs = this.multiaddrsMap.get(path[path.length - 2]);
1537
- if (addrs && addrs.length > 0) {
1538
- const addressesToDial = addrs.sort((a, b) => {
1539
- if (a.includes("/wss/")) {
1540
- if (b.includes("/wss/")) {
1541
- return 0;
1542
- }
1543
- return -1;
1544
- }
1545
- if (a.includes("/ws/")) {
1546
- if (b.includes("/ws/")) {
1547
- return 0;
1548
- }
1549
- if (b.includes("/wss/")) {
1550
- return 1;
1551
- }
1552
- return -1;
1553
- }
1554
- return 0;
1555
- });
1556
-
1557
- for (const addr of addressesToDial) {
1558
- const circuitAddress = multiaddr(
1559
- addr + "/p2p-circuit/webrtc/p2p/" + toPeerId.toString()
1560
- );
1561
- try {
1562
- await this.components.connectionManager.openConnection(
1563
- circuitAddress
1564
- );
1565
- return;
1566
- } catch (error: any) {
1567
- logger.error(
1568
- "Failed to connect directly to: " +
1569
- circuitAddress.toString() +
1570
- ". " +
1571
- error?.message
1572
- );
1573
- }
1574
- }
1704
+ await this.components.connectionManager.openConnection(addresses);
1705
+ } catch (error: any) {
1706
+ logger.info(
1707
+ "Failed to connect directly to: " +
1708
+ JSON.stringify(addresses.map((x) => x.toString())) +
1709
+ ". " +
1710
+ error?.message
1711
+ );
1575
1712
  }
1576
1713
  }
1577
1714
  }
1578
1715
 
1579
- async waitFor(peer: PeerId | PublicSignKey) {
1716
+ async waitFor(
1717
+ peer: PeerId | PublicSignKey,
1718
+ options?: { signal: AbortSignal }
1719
+ ) {
1580
1720
  const hash = (
1581
1721
  peer instanceof PublicSignKey ? peer : getPublicKeyFromPeerId(peer)
1582
1722
  ).hashcode();
1583
1723
  try {
1584
- await waitFor(() => {
1585
- if (!this.peers.has(hash)) {
1586
- return false;
1587
- }
1588
- if (!this.routes.hasLink(this.publicKeyHash, hash)) {
1589
- return false;
1590
- }
1724
+ await waitFor(
1725
+ () => {
1726
+ if (!this.peers.has(hash)) {
1727
+ return false;
1728
+ }
1729
+ if (!this.routes.isReachable(this.publicKeyHash, hash)) {
1730
+ return false;
1731
+ }
1591
1732
 
1592
- return true;
1593
- });
1733
+ return true;
1734
+ },
1735
+ {
1736
+ signal: options?.signal,
1737
+ timeout: 10 * 1000
1738
+ }
1739
+ );
1594
1740
  } catch (error) {
1595
1741
  throw new Error(
1596
1742
  "Stream to " +
@@ -1598,12 +1744,16 @@ export abstract class DirectStream<
1598
1744
  " does not exist. Connection exist: " +
1599
1745
  this.peers.has(hash) +
1600
1746
  ". Route exist: " +
1601
- this.routes.hasLink(this.publicKeyHash, hash)
1747
+ this.routes.isReachable(this.publicKeyHash, hash)
1602
1748
  );
1603
1749
  }
1604
1750
  const stream = this.peers.get(hash)!;
1605
1751
  try {
1606
- await waitFor(() => stream.isReadable && stream.isWritable);
1752
+ // Dontwait for readlable https://github.com/libp2p/js-libp2p/issues/2321
1753
+ await waitFor(() => /* stream.isReadable && */ stream.isWritable, {
1754
+ signal: options?.signal,
1755
+ timeout: 10 * 1000
1756
+ });
1607
1757
  } catch (error) {
1608
1758
  throw new Error(
1609
1759
  "Stream to " +
@@ -1615,6 +1765,66 @@ export abstract class DirectStream<
1615
1765
  );
1616
1766
  }
1617
1767
  }
1768
+
1769
+ get pending(): boolean {
1770
+ return this._ackCallbacks.size > 0;
1771
+ }
1772
+
1773
+ lastQueuedBytes = 0;
1774
+
1775
+ // make this into a job? run every few ms
1776
+ maybePruneConnections(): Promise<void> {
1777
+ if (this.connectionManagerOptions.pruner) {
1778
+ if (this.connectionManagerOptions.pruner.bandwidth != null) {
1779
+ let usedBandwidth = 0;
1780
+ for (const [_k, v] of this.peers) {
1781
+ usedBandwidth += v.usedBandwidth;
1782
+ }
1783
+ usedBandwidth /= this.peers.size;
1784
+
1785
+ if (usedBandwidth > this.connectionManagerOptions.pruner.bandwidth) {
1786
+ // prune
1787
+ return this.pruneConnections();
1788
+ }
1789
+ } else if (this.connectionManagerOptions.pruner.maxBuffer != null) {
1790
+ const queuedBytes = this.getQueuedBytes();
1791
+ if (queuedBytes > this.connectionManagerOptions.pruner.maxBuffer) {
1792
+ // prune
1793
+ return this.pruneConnections();
1794
+ }
1795
+ }
1796
+ }
1797
+
1798
+ return Promise.resolve();
1799
+ }
1800
+
1801
+ async pruneConnections(): Promise<void> {
1802
+ // TODO sort by bandwidth
1803
+ if (this.peers.size <= this.connectionManagerOptions.minConnections) {
1804
+ return;
1805
+ }
1806
+ const sorted = [...this.peers.values()]
1807
+ .sort((x, y) => x.usedBandwidth - y.usedBandwidth)
1808
+ .map((x) => x.publicKey.hashcode());
1809
+ const prunables = this.routes.getPrunable(sorted);
1810
+ if (prunables.length === 0) {
1811
+ return;
1812
+ }
1813
+
1814
+ const stream = this.peers.get(prunables[0])!;
1815
+ this.prunedConnectionsCache!.add(stream.publicKey.hashcode());
1816
+
1817
+ await this.onPeerDisconnected(stream.peerId);
1818
+ return this.components.connectionManager.closeConnections(stream.peerId);
1819
+ }
1820
+
1821
+ getQueuedBytes(): number {
1822
+ let sum = 0;
1823
+ for (const peer of this.peers) {
1824
+ sum += peer[1].outboundStream?.readableLength || 0;
1825
+ }
1826
+ return sum;
1827
+ }
1618
1828
  }
1619
1829
 
1620
1830
  export const waitForPeers = async (