@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/lib/esm/index.d.ts +82 -71
- package/lib/esm/index.js +643 -602
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/logger.d.ts +1 -1
- package/lib/esm/logger.js +1 -1
- package/lib/esm/logger.js.map +1 -1
- package/lib/esm/metrics.d.ts +6 -25
- package/lib/esm/metrics.js +14 -58
- package/lib/esm/metrics.js.map +1 -1
- package/lib/esm/routes.d.ts +47 -35
- package/lib/esm/routes.js +214 -192
- package/lib/esm/routes.js.map +1 -1
- package/package.json +9 -13
- package/src/index.ts +976 -766
- package/src/logger.ts +1 -1
- package/src/metrics.ts +11 -63
- package/src/routes.ts +279 -228
- package/lib/esm/peer-map.d.ts +0 -1
- package/lib/esm/peer-map.js +0 -2
- package/lib/esm/peer-map.js.map +0 -1
- package/src/peer-map.ts +0 -1
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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<
|
|
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 ?
|
|
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:
|
|
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.
|
|
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(
|
|
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.
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
309
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
|
|
364
|
-
private
|
|
365
|
-
private
|
|
366
|
-
private
|
|
367
|
-
private
|
|
368
|
-
|
|
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
|
|
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.
|
|
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:
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
527
|
+
await ready;
|
|
426
528
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
|
579
|
+
await this.onPeerConnected(conn.remotePeer, conn);
|
|
480
580
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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 (
|
|
598
|
-
|
|
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
|
-
|
|
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
|
|
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!,
|
|
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
|
-
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
await this.publishMessage(
|
|
856
|
-
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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:
|
|
1016
|
+
from: PublicSignKey,
|
|
1039
1017
|
peerStream: PeerStreams,
|
|
1040
1018
|
msg: Uint8ArrayList
|
|
1041
1019
|
) {
|
|
1042
|
-
if (!
|
|
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
|
|
1061
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1073
|
-
|
|
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
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
|
|
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
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
return false;
|
|
1127
|
-
}
|
|
1142
|
+
public async maybeVerifyMessage(message: DataMessage) {
|
|
1143
|
+
return message.verify(this.signaturePolicy === "StictSign");
|
|
1144
|
+
}
|
|
1128
1145
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
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
|
-
|
|
1163
|
-
await this.relayMessage(from, message);
|
|
1164
|
-
return true;
|
|
1199
|
+
return this.onDataMessage(from, peerStream, message, seenBefore);
|
|
1165
1200
|
}
|
|
1166
1201
|
|
|
1167
|
-
async
|
|
1168
|
-
|
|
1169
|
-
|
|
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
|
-
|
|
1174
|
-
|
|
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
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
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
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
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
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
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
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
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
|
-
|
|
1273
|
-
|
|
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
|
|
1311
|
+
data: Uint8Array | Uint8ArrayList | undefined,
|
|
1312
|
+
options: WithTo | WithMode
|
|
1282
1313
|
) {
|
|
1283
1314
|
// dispatch the event if we are interested
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
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
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
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
|
-
|
|
1357
|
+
header: new MessageHeader({ mode })
|
|
1307
1358
|
});
|
|
1308
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
1390
|
+
from: PublicSignKey,
|
|
1358
1391
|
message: Message,
|
|
1359
|
-
to?: 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("
|
|
1406
|
+
logger.debug("Received a message to relay but canRelayMessage is false");
|
|
1365
1407
|
}
|
|
1366
1408
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1409
|
+
|
|
1410
|
+
private async createDeliveryPromise(
|
|
1411
|
+
from: PublicSignKey,
|
|
1412
|
+
message: DataMessage,
|
|
1371
1413
|
relayed?: boolean
|
|
1372
|
-
): Promise<void> {
|
|
1373
|
-
if (message instanceof
|
|
1374
|
-
|
|
1375
|
-
|
|
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
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
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
|
-
|
|
1427
|
-
|
|
1543
|
+
arr.push(seenCounter);
|
|
1544
|
+
|
|
1545
|
+
uniqueAcks.add(messageTargetHash + seenCounter);
|
|
1428
1546
|
}
|
|
1429
1547
|
}
|
|
1548
|
+
}
|
|
1430
1549
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
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
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
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
|
-
|
|
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[] |
|
|
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("
|
|
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.
|
|
1671
|
+
if (id.publicKey.equals(from)) {
|
|
1481
1672
|
continue;
|
|
1482
1673
|
}
|
|
1483
1674
|
|
|
1484
|
-
|
|
1485
|
-
|
|
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
|
|
1498
|
-
|
|
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(
|
|
1503
|
-
if (
|
|
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
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
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
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
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(
|
|
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
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
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
|
-
|
|
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.
|
|
1747
|
+
this.routes.isReachable(this.publicKeyHash, hash)
|
|
1602
1748
|
);
|
|
1603
1749
|
}
|
|
1604
1750
|
const stream = this.peers.get(hash)!;
|
|
1605
1751
|
try {
|
|
1606
|
-
|
|
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 (
|