@peerbit/stream 1.0.20 → 2.0.2
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/lib/esm/index.js
CHANGED
|
@@ -1,23 +1,40 @@
|
|
|
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 { pushable } from "it-pushable";
|
|
5
5
|
import { Uint8ArrayList } from "uint8arraylist";
|
|
6
6
|
import { abortableSource } from "abortable-iterator";
|
|
7
7
|
import * as lp from "it-length-prefixed";
|
|
8
|
-
import { Routes } from "./routes.js";
|
|
8
|
+
import { MAX_ROUTE_DISTANCE, Routes } from "./routes.js";
|
|
9
|
+
import pDefer from "p-defer";
|
|
10
|
+
import { AbortError, delay, TimeoutError, waitFor } from "@peerbit/time";
|
|
11
|
+
import { getKeypairFromPeerId, getPublicKeyFromPeerId, PublicSignKey, sha256Base64, toBase64 } from "@peerbit/crypto";
|
|
9
12
|
import { multiaddr } from "@multiformats/multiaddr";
|
|
10
|
-
import { delay, TimeoutError, waitFor, waitForAsync } from "@peerbit/time";
|
|
11
|
-
import { getKeypairFromPeerId, getPublicKeyFromPeerId, PublicSignKey } from "@peerbit/crypto";
|
|
12
13
|
import { logger } from "./logger.js";
|
|
13
|
-
import { Cache } from "@peerbit/cache";
|
|
14
14
|
export { logger };
|
|
15
|
-
import {
|
|
15
|
+
import { Cache } from "@peerbit/cache";
|
|
16
|
+
import { ready } from "@peerbit/crypto";
|
|
17
|
+
import { Message as Message, DataMessage, getMsgId, ACK, SeekDelivery, AcknowledgeDelivery, SilentDelivery, MessageHeader, Goodbye, TracedDelivery, AnyWhere } from "@peerbit/stream-interface";
|
|
18
|
+
import { MultiAddrinfo } from "@peerbit/stream-interface";
|
|
19
|
+
import { MovingAverageTracker } from "./metrics.js";
|
|
20
|
+
const logError = (e) => {
|
|
21
|
+
return logger.error(e?.message);
|
|
22
|
+
};
|
|
23
|
+
const DEFAULT_SEEK_MESSAGE_REDUDANCY = 2;
|
|
24
|
+
const DEFAULT_SILENT_MESSAGE_REDUDANCY = 1;
|
|
16
25
|
const isWebsocketConnection = (c) => c.remoteAddr.protoNames().find((x) => x === "ws" || x === "wss");
|
|
26
|
+
const SEEK_DELIVERY_TIMEOUT = 15e3;
|
|
27
|
+
const MAX_DATA_LENGTH = 1e7 + 1000; // 10 mb and some metadata
|
|
28
|
+
const MAX_QUEUED_BYTES = MAX_DATA_LENGTH * 50;
|
|
29
|
+
const DEFAULT_PRUNE_CONNECTIONS_INTERVAL = 2e4;
|
|
30
|
+
const DEFAULT_MIN_CONNECTIONS = 2;
|
|
31
|
+
const DEFAULT_MAX_CONNECTIONS = 300;
|
|
32
|
+
const DEFAULT_PRUNED_CONNNECTIONS_TIMEOUT = 30 * 1000;
|
|
33
|
+
const ROUTE_UPDATE_DELAY_FACTOR = 3e4;
|
|
17
34
|
/**
|
|
18
35
|
* Thin wrapper around a peer's inbound / outbound pubsub streams
|
|
19
36
|
*/
|
|
20
|
-
export class PeerStreams extends
|
|
37
|
+
export class PeerStreams extends TypedEventEmitter {
|
|
21
38
|
counter = 0;
|
|
22
39
|
peerId;
|
|
23
40
|
publicKey;
|
|
@@ -43,9 +60,9 @@ export class PeerStreams extends EventEmitter {
|
|
|
43
60
|
*/
|
|
44
61
|
inboundAbortController;
|
|
45
62
|
closed;
|
|
46
|
-
pingJob;
|
|
47
|
-
pingLatency;
|
|
48
63
|
connId;
|
|
64
|
+
seekedOnce;
|
|
65
|
+
usedBandWidthTracker;
|
|
49
66
|
constructor(init) {
|
|
50
67
|
super();
|
|
51
68
|
this.peerId = init.peerId;
|
|
@@ -55,6 +72,7 @@ export class PeerStreams extends EventEmitter {
|
|
|
55
72
|
this.closed = false;
|
|
56
73
|
this.connId = init.connId;
|
|
57
74
|
this.counter = 1;
|
|
75
|
+
this.usedBandWidthTracker = new MovingAverageTracker();
|
|
58
76
|
}
|
|
59
77
|
/**
|
|
60
78
|
* Do we have a connection to read from?
|
|
@@ -68,6 +86,9 @@ export class PeerStreams extends EventEmitter {
|
|
|
68
86
|
get isWritable() {
|
|
69
87
|
return Boolean(this.outboundStream);
|
|
70
88
|
}
|
|
89
|
+
get usedBandwidth() {
|
|
90
|
+
return this.usedBandWidthTracker.value;
|
|
91
|
+
}
|
|
71
92
|
/**
|
|
72
93
|
* Send a message to this peer.
|
|
73
94
|
* Throws if there is no `stream` to write to available.
|
|
@@ -77,9 +98,14 @@ export class PeerStreams extends EventEmitter {
|
|
|
77
98
|
logger.error("No writable connection to " + this.peerId.toString());
|
|
78
99
|
throw new Error("No writable connection to " + this.peerId.toString());
|
|
79
100
|
}
|
|
80
|
-
this.
|
|
101
|
+
this.usedBandWidthTracker.add(data.byteLength);
|
|
102
|
+
this.outboundStream.push(data instanceof Uint8Array ? data : data.subarray());
|
|
81
103
|
}
|
|
82
104
|
async waitForWrite(bytes) {
|
|
105
|
+
if (this.closed) {
|
|
106
|
+
logger.error("Failed to send to stream: " + this.peerId + ". Closed");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
83
109
|
if (!this.isWritable) {
|
|
84
110
|
// Catch the event where the outbound stream is attach, but also abort if we shut down
|
|
85
111
|
const outboundPromise = new Promise((rs, rj) => {
|
|
@@ -133,7 +159,7 @@ export class PeerStreams extends EventEmitter {
|
|
|
133
159
|
// - abortable, set to only return on abort, rather than throw
|
|
134
160
|
// - transformed with length-prefix transform
|
|
135
161
|
this._rawInboundStream = stream;
|
|
136
|
-
this.inboundStream = abortableSource(pipe(this._rawInboundStream, (source) => lp.decode(source, { maxDataLength:
|
|
162
|
+
this.inboundStream = abortableSource(pipe(this._rawInboundStream, (source) => lp.decode(source, { maxDataLength: MAX_DATA_LENGTH })), this.inboundAbortController.signal, {
|
|
137
163
|
returnOnAbort: true,
|
|
138
164
|
onReturnError: (err) => {
|
|
139
165
|
logger.error("Inbound stream error", err?.message);
|
|
@@ -149,9 +175,8 @@ export class PeerStreams extends EventEmitter {
|
|
|
149
175
|
// If an outbound stream already exists, gently close it
|
|
150
176
|
const _prevStream = this.outboundStream;
|
|
151
177
|
this._rawOutboundStream = stream;
|
|
152
|
-
this.pingJob?.abort();
|
|
153
178
|
this.outboundStream = pushable({
|
|
154
|
-
objectMode:
|
|
179
|
+
objectMode: false,
|
|
155
180
|
onEnd: () => {
|
|
156
181
|
return stream.close().then(() => {
|
|
157
182
|
if (this._rawOutboundStream === stream) {
|
|
@@ -162,9 +187,7 @@ export class PeerStreams extends EventEmitter {
|
|
|
162
187
|
});
|
|
163
188
|
}
|
|
164
189
|
});
|
|
165
|
-
pipe(this.outboundStream, (source) => lp.encode(source), this._rawOutboundStream).catch(
|
|
166
|
-
logger.error(err);
|
|
167
|
-
});
|
|
190
|
+
pipe(this.outboundStream, (source) => lp.encode(source), this._rawOutboundStream).catch(logError);
|
|
168
191
|
// Emit if the connection is new
|
|
169
192
|
this.dispatchEvent(new CustomEvent("stream:outbound"));
|
|
170
193
|
if (_prevStream != null) {
|
|
@@ -191,19 +214,16 @@ export class PeerStreams extends EventEmitter {
|
|
|
191
214
|
this.inboundAbortController.abort();
|
|
192
215
|
await this._rawInboundStream?.close();
|
|
193
216
|
}
|
|
194
|
-
this.
|
|
195
|
-
this.pingLatency = undefined;
|
|
196
|
-
//this.dispatchEvent(new CustomEvent('close'))
|
|
217
|
+
this.dispatchEvent(new CustomEvent("close"));
|
|
197
218
|
this._rawOutboundStream = undefined;
|
|
198
219
|
this.outboundStream = undefined;
|
|
199
220
|
this._rawInboundStream = undefined;
|
|
200
221
|
this.inboundStream = undefined;
|
|
201
222
|
}
|
|
202
223
|
}
|
|
203
|
-
export class DirectStream extends
|
|
224
|
+
export class DirectStream extends TypedEventEmitter {
|
|
204
225
|
components;
|
|
205
226
|
peerId;
|
|
206
|
-
peerIdStr;
|
|
207
227
|
publicKey;
|
|
208
228
|
publicKeyHash;
|
|
209
229
|
sign;
|
|
@@ -213,7 +233,6 @@ export class DirectStream extends EventEmitter {
|
|
|
213
233
|
*/
|
|
214
234
|
peers;
|
|
215
235
|
peerKeyHashToPublicKey;
|
|
216
|
-
peerIdToPublicKey;
|
|
217
236
|
routes;
|
|
218
237
|
/**
|
|
219
238
|
* If router can relay received messages, even if not subscribed
|
|
@@ -223,82 +242,110 @@ export class DirectStream extends EventEmitter {
|
|
|
223
242
|
* if publish should emit to self, if subscribed
|
|
224
243
|
*/
|
|
225
244
|
signaturePolicy;
|
|
226
|
-
emitSelf;
|
|
227
245
|
queue;
|
|
228
246
|
multicodecs;
|
|
229
247
|
seenCache;
|
|
230
|
-
earlyGoodbyes;
|
|
231
|
-
helloMap; // key is hash of publicKey, value is map whey key is hash of signature bytes, and value is latest Hello
|
|
232
|
-
multiaddrsMap;
|
|
233
248
|
_registrarTopologyIds;
|
|
234
249
|
maxInboundStreams;
|
|
235
250
|
maxOutboundStreams;
|
|
236
|
-
topology;
|
|
237
|
-
pingJobPromise;
|
|
238
|
-
pingJob;
|
|
239
|
-
pingInterval;
|
|
240
251
|
connectionManagerOptions;
|
|
241
252
|
recentDials;
|
|
253
|
+
healthChecks;
|
|
254
|
+
pruneConnectionsTimeout;
|
|
255
|
+
prunedConnectionsCache;
|
|
256
|
+
routeSeekInterval;
|
|
257
|
+
seekTimeout;
|
|
258
|
+
closeController;
|
|
259
|
+
_ackCallbacks = new Map();
|
|
242
260
|
constructor(components, multicodecs, options) {
|
|
243
261
|
super();
|
|
244
262
|
this.components = components;
|
|
245
|
-
const { canRelayMessage = false,
|
|
263
|
+
const { canRelayMessage = false, messageProcessingConcurrency = 10, maxInboundStreams, maxOutboundStreams, signaturePolicy = "StictSign", connectionManager, routeSeekInterval = ROUTE_UPDATE_DELAY_FACTOR, seekTimeout = SEEK_DELIVERY_TIMEOUT } = options || {};
|
|
246
264
|
const signKey = getKeypairFromPeerId(components.peerId);
|
|
265
|
+
this.seekTimeout = seekTimeout;
|
|
247
266
|
this.sign = signKey.sign.bind(signKey);
|
|
248
267
|
this.peerId = components.peerId;
|
|
249
|
-
this.peerIdStr = components.peerId.toString();
|
|
250
268
|
this.publicKey = signKey.publicKey;
|
|
251
269
|
this.publicKeyHash = signKey.publicKey.hashcode();
|
|
252
270
|
this.multicodecs = multicodecs;
|
|
253
271
|
this.started = false;
|
|
254
272
|
this.peers = new Map();
|
|
255
|
-
this.helloMap = new Map();
|
|
256
|
-
this.multiaddrsMap = new Map();
|
|
257
273
|
this.routes = new Routes(this.publicKeyHash);
|
|
258
274
|
this.canRelayMessage = canRelayMessage;
|
|
259
|
-
this.
|
|
275
|
+
this.healthChecks = new Map();
|
|
276
|
+
this.routeSeekInterval = routeSeekInterval;
|
|
260
277
|
this.queue = new Queue({ concurrency: messageProcessingConcurrency });
|
|
261
|
-
this.earlyGoodbyes = new Map();
|
|
262
278
|
this.maxInboundStreams = maxInboundStreams;
|
|
263
279
|
this.maxOutboundStreams = maxOutboundStreams;
|
|
264
|
-
this.seenCache = new Cache({ max:
|
|
280
|
+
this.seenCache = new Cache({ max: 1e6, ttl: 10 * 60 * 1e3 });
|
|
265
281
|
this.peerKeyHashToPublicKey = new Map();
|
|
266
|
-
this.peerIdToPublicKey = new Map();
|
|
267
|
-
this.pingInterval = pingInterval;
|
|
268
282
|
this._onIncomingStream = this._onIncomingStream.bind(this);
|
|
269
283
|
this.onPeerConnected = this.onPeerConnected.bind(this);
|
|
270
284
|
this.onPeerDisconnected = this.onPeerDisconnected.bind(this);
|
|
271
285
|
this.signaturePolicy = signaturePolicy;
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
286
|
+
if (connectionManager === false || connectionManager === null) {
|
|
287
|
+
this.connectionManagerOptions = {
|
|
288
|
+
maxConnections: Number.MAX_SAFE_INTEGER,
|
|
289
|
+
minConnections: 0,
|
|
290
|
+
dialer: undefined,
|
|
291
|
+
pruner: undefined
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
this.connectionManagerOptions = {
|
|
296
|
+
maxConnections: DEFAULT_MAX_CONNECTIONS,
|
|
297
|
+
minConnections: DEFAULT_MIN_CONNECTIONS,
|
|
298
|
+
...connectionManager,
|
|
299
|
+
dialer: connectionManager?.dialer !== false &&
|
|
300
|
+
connectionManager?.dialer !== null
|
|
301
|
+
? { retryDelay: 60 * 1000, ...connectionManager?.dialer }
|
|
302
|
+
: undefined,
|
|
303
|
+
pruner: connectionManager?.pruner !== false &&
|
|
304
|
+
connectionManager?.pruner !== null
|
|
305
|
+
? {
|
|
306
|
+
connectionTimeout: DEFAULT_PRUNED_CONNNECTIONS_TIMEOUT,
|
|
307
|
+
interval: DEFAULT_PRUNE_CONNECTIONS_INTERVAL,
|
|
308
|
+
maxBuffer: MAX_QUEUED_BYTES,
|
|
309
|
+
...connectionManager?.pruner
|
|
310
|
+
}
|
|
311
|
+
: undefined
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
this.recentDials = this.connectionManagerOptions.dialer
|
|
315
|
+
? new Cache({
|
|
316
|
+
ttl: this.connectionManagerOptions.dialer.retryDelay,
|
|
317
|
+
max: 1e3
|
|
318
|
+
})
|
|
319
|
+
: undefined;
|
|
320
|
+
this.prunedConnectionsCache = this.connectionManagerOptions.pruner
|
|
321
|
+
? new Cache({
|
|
322
|
+
max: 1e6,
|
|
323
|
+
ttl: this.connectionManagerOptions.pruner.connectionTimeout
|
|
324
|
+
})
|
|
325
|
+
: undefined;
|
|
277
326
|
}
|
|
278
327
|
async start() {
|
|
279
328
|
if (this.started) {
|
|
280
329
|
return;
|
|
281
330
|
}
|
|
331
|
+
await ready;
|
|
332
|
+
this.closeController = new AbortController();
|
|
333
|
+
this.started = true;
|
|
282
334
|
logger.debug("starting");
|
|
283
|
-
// register protocol with topology
|
|
284
|
-
// Topology callbacks called on connection manager changes
|
|
285
|
-
this._registrarTopologyIds = await Promise.all(this.multicodecs.map((multicodec) => this.components.registrar.register(multicodec, {
|
|
286
|
-
onConnect: this.onPeerConnected.bind(this),
|
|
287
|
-
onDisconnect: this.onPeerDisconnected.bind(this)
|
|
288
|
-
})));
|
|
289
335
|
// Incoming streams
|
|
290
336
|
// Called after a peer dials us
|
|
291
337
|
await Promise.all(this.multicodecs.map((multicodec) => this.components.registrar.handle(multicodec, this._onIncomingStream, {
|
|
292
338
|
maxInboundStreams: this.maxInboundStreams,
|
|
293
|
-
maxOutboundStreams: this.maxOutboundStreams
|
|
339
|
+
maxOutboundStreams: this.maxOutboundStreams,
|
|
340
|
+
runOnTransientConnection: false
|
|
341
|
+
})));
|
|
342
|
+
// register protocol with topology
|
|
343
|
+
// Topology callbacks called on connection manager changes
|
|
344
|
+
this._registrarTopologyIds = await Promise.all(this.multicodecs.map((multicodec) => this.components.registrar.register(multicodec, {
|
|
345
|
+
onConnect: this.onPeerConnected.bind(this),
|
|
346
|
+
onDisconnect: this.onPeerDisconnected.bind(this),
|
|
347
|
+
notifyOnTransient: false
|
|
294
348
|
})));
|
|
295
|
-
// TODO remove/modify when https://github.com/libp2p/js-libp2p/issues/2036 is resolved
|
|
296
|
-
this.components.events.addEventListener("connection:open", (e) => {
|
|
297
|
-
if (e.detail.multiplexer === "/webrtc") {
|
|
298
|
-
this.onPeerConnected(e.detail.remotePeer, e.detail);
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
this.started = true;
|
|
302
349
|
// 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)
|
|
303
350
|
const peerToConnections = new Map();
|
|
304
351
|
const connections = this.components.connectionManager.getConnections();
|
|
@@ -319,35 +366,24 @@ export class DirectStream extends EventEmitter {
|
|
|
319
366
|
break;
|
|
320
367
|
}
|
|
321
368
|
}
|
|
322
|
-
await this.onPeerConnected(conn.remotePeer, conn
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const promises = [];
|
|
328
|
-
this.peers.forEach((peer) => {
|
|
329
|
-
promises.push(this.ping(peer).catch((e) => {
|
|
330
|
-
if (e instanceof TimeoutError) {
|
|
331
|
-
// Ignore
|
|
332
|
-
}
|
|
333
|
-
else {
|
|
334
|
-
logger.error(e);
|
|
335
|
-
}
|
|
336
|
-
}));
|
|
337
|
-
});
|
|
338
|
-
promises.push(this.hello()); // Repetedly say hello to everyone to create traces in the network to measure latencies
|
|
339
|
-
this.pingJobPromise = Promise.all(promises)
|
|
340
|
-
.catch((e) => {
|
|
341
|
-
logger.error(e?.message);
|
|
342
|
-
})
|
|
343
|
-
.finally(() => {
|
|
344
|
-
if (!this.started || !this.pingInterval) {
|
|
369
|
+
await this.onPeerConnected(conn.remotePeer, conn);
|
|
370
|
+
}
|
|
371
|
+
if (this.connectionManagerOptions.pruner) {
|
|
372
|
+
const pruneConnectionsLoop = () => {
|
|
373
|
+
if (!this.connectionManagerOptions.pruner) {
|
|
345
374
|
return;
|
|
346
375
|
}
|
|
347
|
-
this.
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
376
|
+
this.pruneConnectionsTimeout = setTimeout(() => {
|
|
377
|
+
this.maybePruneConnections().finally(() => {
|
|
378
|
+
if (!this.started) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
pruneConnectionsLoop();
|
|
382
|
+
});
|
|
383
|
+
}, this.connectionManagerOptions.pruner.interval);
|
|
384
|
+
};
|
|
385
|
+
pruneConnectionsLoop();
|
|
386
|
+
}
|
|
351
387
|
}
|
|
352
388
|
/**
|
|
353
389
|
* Unregister the pubsub protocol and the streams with other peers will be closed.
|
|
@@ -357,26 +393,31 @@ export class DirectStream extends EventEmitter {
|
|
|
357
393
|
return;
|
|
358
394
|
}
|
|
359
395
|
this.started = false;
|
|
360
|
-
|
|
361
|
-
|
|
396
|
+
this.closeController.abort();
|
|
397
|
+
clearTimeout(this.pruneConnectionsTimeout);
|
|
362
398
|
await Promise.all(this.multicodecs.map((x) => this.components.registrar.unhandle(x)));
|
|
363
399
|
logger.debug("stopping");
|
|
364
400
|
for (const peerStreams of this.peers.values()) {
|
|
365
401
|
await peerStreams.close();
|
|
366
402
|
}
|
|
403
|
+
for (const [k, v] of this.healthChecks) {
|
|
404
|
+
clearTimeout(v);
|
|
405
|
+
}
|
|
406
|
+
this.healthChecks.clear();
|
|
407
|
+
this.prunedConnectionsCache?.clear();
|
|
367
408
|
// unregister protocol and handlers
|
|
368
409
|
if (this._registrarTopologyIds != null) {
|
|
369
410
|
this._registrarTopologyIds?.map((id) => this.components.registrar.unregister(id));
|
|
370
411
|
}
|
|
371
412
|
this.queue.clear();
|
|
372
|
-
this.helloMap.clear();
|
|
373
|
-
this.multiaddrsMap.clear();
|
|
374
|
-
this.earlyGoodbyes.clear();
|
|
375
413
|
this.peers.clear();
|
|
376
414
|
this.seenCache.clear();
|
|
377
415
|
this.routes.clear();
|
|
378
416
|
this.peerKeyHashToPublicKey.clear();
|
|
379
|
-
this.
|
|
417
|
+
for (const [k, v] of this._ackCallbacks) {
|
|
418
|
+
clearTimeout(v.timeout);
|
|
419
|
+
}
|
|
420
|
+
this._ackCallbacks.clear();
|
|
380
421
|
logger.debug("stopped");
|
|
381
422
|
}
|
|
382
423
|
isStarted() {
|
|
@@ -396,65 +437,32 @@ export class DirectStream extends EventEmitter {
|
|
|
396
437
|
return;
|
|
397
438
|
}
|
|
398
439
|
const publicKey = getPublicKeyFromPeerId(peerId);
|
|
440
|
+
if (this.prunedConnectionsCache?.has(publicKey.hashcode())) {
|
|
441
|
+
await connection.close();
|
|
442
|
+
await this.components.peerStore.delete(peerId);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
399
445
|
const peer = this.addPeer(peerId, publicKey, stream.protocol, connection.id);
|
|
400
446
|
const inboundStream = peer.attachInboundStream(stream);
|
|
401
|
-
this.processMessages(
|
|
402
|
-
logger.error(err);
|
|
403
|
-
});
|
|
447
|
+
this.processMessages(peer.publicKey, inboundStream, peer).catch(logError);
|
|
404
448
|
}
|
|
405
449
|
/**
|
|
406
450
|
* Registrar notifies an established connection with protocol
|
|
407
451
|
*/
|
|
408
|
-
async onPeerConnected(peerId,
|
|
409
|
-
if (
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (!this.isStarted() || conn.status !== "open") {
|
|
452
|
+
async onPeerConnected(peerId, connection) {
|
|
453
|
+
if (!this.isStarted() ||
|
|
454
|
+
connection.transient ||
|
|
455
|
+
connection.status !== "open") {
|
|
413
456
|
return;
|
|
414
457
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
};
|
|
421
|
-
this.addEventListener("close", close);
|
|
422
|
-
const result = await waitForAsync(async () => {
|
|
423
|
-
try {
|
|
424
|
-
const hasProtocol = await this.components.peerStore
|
|
425
|
-
.get(peerId)
|
|
426
|
-
.then((x) => this.multicodecs.find((y) => x.protocols.includes(y)));
|
|
427
|
-
if (!hasProtocol) {
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
catch (error) {
|
|
432
|
-
if (error.code === "ERR_NOT_FOUND") {
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
throw error;
|
|
436
|
-
}
|
|
437
|
-
return true;
|
|
438
|
-
}, {
|
|
439
|
-
delayInterval: 100,
|
|
440
|
-
timeout: 1e4,
|
|
441
|
-
stopperCallback: (cb) => {
|
|
442
|
-
closeFn = cb;
|
|
443
|
-
}
|
|
444
|
-
}).finally(() => {
|
|
445
|
-
this.removeEventListener("close", close);
|
|
446
|
-
});
|
|
447
|
-
if (!result) {
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
catch (error) {
|
|
452
|
-
return;
|
|
458
|
+
const peerKey = getPublicKeyFromPeerId(peerId);
|
|
459
|
+
if (this.prunedConnectionsCache?.has(peerKey.hashcode())) {
|
|
460
|
+
await connection.close();
|
|
461
|
+
await this.components.peerStore.delete(peerId);
|
|
462
|
+
return; // we recently pruned this connect, dont allow it to connect for a while
|
|
453
463
|
}
|
|
454
464
|
try {
|
|
455
|
-
const
|
|
456
|
-
const peerKeyHash = peerKey.hashcode();
|
|
457
|
-
for (const existingStreams of conn.streams) {
|
|
465
|
+
for (const existingStreams of connection.streams) {
|
|
458
466
|
if (existingStreams.protocol &&
|
|
459
467
|
this.multicodecs.includes(existingStreams.protocol) &&
|
|
460
468
|
existingStreams.direction === "outbound") {
|
|
@@ -473,35 +481,20 @@ export class DirectStream extends EventEmitter {
|
|
|
473
481
|
return;
|
|
474
482
|
}
|
|
475
483
|
try {
|
|
476
|
-
stream = await
|
|
484
|
+
stream = await connection.newStream(this.multicodecs);
|
|
477
485
|
if (stream.protocol == null) {
|
|
478
486
|
stream.abort(new Error("Stream was not multiplexed"));
|
|
479
487
|
return;
|
|
480
488
|
}
|
|
481
|
-
peer = this.addPeer(peerId, peerKey, stream.protocol,
|
|
489
|
+
peer = this.addPeer(peerId, peerKey, stream.protocol, connection.id); // TODO types
|
|
482
490
|
await peer.attachOutboundStream(stream);
|
|
483
|
-
// TODO do we want to do this?
|
|
484
|
-
/* if (!peer.inboundStream) {
|
|
485
|
-
const inboundStream = conn.streams.find(
|
|
486
|
-
(x) =>
|
|
487
|
-
x.stat.protocol &&
|
|
488
|
-
this.multicodecs.includes(x.stat.protocol) &&
|
|
489
|
-
x.stat.direction === "inbound"
|
|
490
|
-
);
|
|
491
|
-
if (inboundStream) {
|
|
492
|
-
this._onIncomingStream({
|
|
493
|
-
connection: conn,
|
|
494
|
-
stream: inboundStream,
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
} */
|
|
498
491
|
}
|
|
499
492
|
catch (error) {
|
|
500
493
|
if (error.code === "ERR_UNSUPPORTED_PROTOCOL") {
|
|
501
494
|
await delay(100);
|
|
502
495
|
continue; // Retry
|
|
503
496
|
}
|
|
504
|
-
if (
|
|
497
|
+
if (connection.status !== "open" ||
|
|
505
498
|
error?.message === "Muxer already closed" ||
|
|
506
499
|
error.code === "ERR_STREAM_RESET") {
|
|
507
500
|
return; // fail silenty
|
|
@@ -513,80 +506,12 @@ export class DirectStream extends EventEmitter {
|
|
|
513
506
|
if (!stream) {
|
|
514
507
|
return;
|
|
515
508
|
}
|
|
516
|
-
|
|
517
|
-
return; // we return here because we will enter this method once more once the protocol has been registered for the remote peer
|
|
518
|
-
}
|
|
519
|
-
// Add connection with assumed large latency
|
|
520
|
-
this.peerIdToPublicKey.set(peerId.toString(), peerKey);
|
|
521
|
-
const promises = [];
|
|
522
|
-
/* if (!existingStream) */ {
|
|
523
|
-
this.addRouteConnection(this.publicKey, peerKey, Number.MAX_SAFE_INTEGER);
|
|
524
|
-
// Get accurate latency
|
|
525
|
-
promises.push(this.ping(peer));
|
|
526
|
-
// Say hello
|
|
527
|
-
promises.push(this.publishMessage(this.components.peerId, await new Hello({
|
|
528
|
-
multiaddrs: this.components.addressManager
|
|
529
|
-
.getAddresses()
|
|
530
|
-
.map((x) => x.toString())
|
|
531
|
-
}).sign(this.sign), [peer]));
|
|
532
|
-
// Send my goodbye early if I disconnect for some reason, (so my peer can say goodbye for me)
|
|
533
|
-
// TODO add custom condition fn for doing below
|
|
534
|
-
promises.push(this.publishMessage(this.components.peerId, await new Goodbye({ early: true }).sign(this.sign), [peer]));
|
|
535
|
-
// replay all hellos
|
|
536
|
-
for (const [sender, hellos] of this.helloMap) {
|
|
537
|
-
if (sender === peerKeyHash) {
|
|
538
|
-
// Don't say hellos from sender to same sender (uneccessary)
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
541
|
-
outer: for (const [key, hello] of hellos) {
|
|
542
|
-
if (!hello.header.verify()) {
|
|
543
|
-
hellos.delete(key);
|
|
544
|
-
}
|
|
545
|
-
for (const signer of hello.signatures.publicKeys) {
|
|
546
|
-
if (!this.routes.hasNode(signer.hashcode())) {
|
|
547
|
-
// purge this hello since it has travelled a path that no longer exist
|
|
548
|
-
hellos.delete(key);
|
|
549
|
-
continue outer;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
promises.push(this.publishMessage(this.components.peerId, hello, [peer]));
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
const resolved = await Promise.all(promises);
|
|
557
|
-
return resolved;
|
|
509
|
+
this.addRouteConnection(this.publicKeyHash, peerKey.hashcode(), peerKey, 0, +new Date());
|
|
558
510
|
}
|
|
559
511
|
catch (err) {
|
|
560
512
|
logger.error(err);
|
|
561
513
|
}
|
|
562
514
|
}
|
|
563
|
-
addRouteConnection(from, to, latency) {
|
|
564
|
-
this.peerKeyHashToPublicKey.set(from.hashcode(), from);
|
|
565
|
-
this.peerKeyHashToPublicKey.set(to.hashcode(), to);
|
|
566
|
-
const links = this.routes.addLink(from.hashcode(), to.hashcode(), latency);
|
|
567
|
-
for (const added of links) {
|
|
568
|
-
const key = this.peerKeyHashToPublicKey.get(added);
|
|
569
|
-
if (key?.equals(this.publicKey) === false) {
|
|
570
|
-
this.onPeerReachable(key);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
removeRouteConnection(from, to) {
|
|
575
|
-
const has = this.routes.hasNode(to.hashcode());
|
|
576
|
-
if (!has) {
|
|
577
|
-
this.onPeerUnreachable(to);
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
const links = this.routes.deleteLink(from.hashcode(), to.hashcode());
|
|
581
|
-
for (const deleted of links) {
|
|
582
|
-
const key = this.peerKeyHashToPublicKey.get(deleted);
|
|
583
|
-
this.peerKeyHashToPublicKey.delete(deleted);
|
|
584
|
-
if (key?.equals(this.publicKey) === false) {
|
|
585
|
-
this.onPeerUnreachable(key);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
515
|
/**
|
|
591
516
|
* Registrar notifies a closing connection with pubsub protocol
|
|
592
517
|
*/
|
|
@@ -608,19 +533,51 @@ export class DirectStream extends EventEmitter {
|
|
|
608
533
|
}
|
|
609
534
|
if (!this.publicKey.equals(peerKey)) {
|
|
610
535
|
await this._removePeer(peerKey);
|
|
611
|
-
this.removeRouteConnection(this.publicKey, peerKey);
|
|
612
536
|
// Notify network
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
await
|
|
617
|
-
|
|
618
|
-
|
|
537
|
+
const dependent = this.routes.getDependent(peerKeyHash);
|
|
538
|
+
this.removeRouteConnection(peerKeyHash, true);
|
|
539
|
+
if (dependent.length > 0) {
|
|
540
|
+
await this.publishMessage(this.publicKey, await new Goodbye({
|
|
541
|
+
leaving: [peerKeyHash],
|
|
542
|
+
header: new MessageHeader({
|
|
543
|
+
mode: new SilentDelivery({ to: dependent, redundancy: 2 })
|
|
544
|
+
})
|
|
545
|
+
}).sign(this.sign));
|
|
619
546
|
}
|
|
620
|
-
this.peerIdToPublicKey.delete(peerId.toString());
|
|
621
547
|
}
|
|
622
548
|
logger.debug("connection ended:" + peerKey.toString());
|
|
623
549
|
}
|
|
550
|
+
removeRouteConnection(hash, neigbour) {
|
|
551
|
+
const unreachable = neigbour
|
|
552
|
+
? this.routes.removeNeighbour(hash)
|
|
553
|
+
: this.routes.removeTarget(hash);
|
|
554
|
+
for (const node of unreachable) {
|
|
555
|
+
this.onPeerUnreachable(node); // TODO types
|
|
556
|
+
this.peerKeyHashToPublicKey.delete(node);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
addRouteConnection(from, neighbour, target, distance, session, pending = false) {
|
|
560
|
+
const targetHash = typeof target === "string" ? target : target.hashcode();
|
|
561
|
+
const wasReachable = from === this.publicKeyHash
|
|
562
|
+
? this.routes.isReachable(from, targetHash)
|
|
563
|
+
: true;
|
|
564
|
+
if (pending) {
|
|
565
|
+
this.routes.addPendingRouteConnection(session, {
|
|
566
|
+
distance,
|
|
567
|
+
from,
|
|
568
|
+
neighbour,
|
|
569
|
+
target: targetHash
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
this.routes.add(from, neighbour, targetHash, distance, session);
|
|
574
|
+
}
|
|
575
|
+
const newPeer = wasReachable === false && this.routes.isReachable(from, targetHash);
|
|
576
|
+
if (newPeer) {
|
|
577
|
+
this.peerKeyHashToPublicKey.set(target.hashcode(), target);
|
|
578
|
+
this.onPeerReachable(target); // TODO types
|
|
579
|
+
}
|
|
580
|
+
}
|
|
624
581
|
/**
|
|
625
582
|
* invoked when a new peer becomes reachable
|
|
626
583
|
* @param publicKeyHash
|
|
@@ -633,17 +590,24 @@ export class DirectStream extends EventEmitter {
|
|
|
633
590
|
* invoked when a new peer becomes unreachable
|
|
634
591
|
* @param publicKeyHash
|
|
635
592
|
*/
|
|
636
|
-
onPeerUnreachable(
|
|
593
|
+
onPeerUnreachable(hash) {
|
|
637
594
|
// override this fn
|
|
638
|
-
this.
|
|
639
|
-
|
|
640
|
-
|
|
595
|
+
this.dispatchEvent(
|
|
596
|
+
// TODO types
|
|
597
|
+
new CustomEvent("peer:unreachable", {
|
|
598
|
+
detail: this.peerKeyHashToPublicKey.get(hash)
|
|
599
|
+
}));
|
|
641
600
|
}
|
|
642
601
|
/**
|
|
643
602
|
* Notifies the router that a peer has been connected
|
|
644
603
|
*/
|
|
645
604
|
addPeer(peerId, publicKey, protocol, connId) {
|
|
646
605
|
const publicKeyHash = publicKey.hashcode();
|
|
606
|
+
const hc = this.healthChecks.get(publicKeyHash);
|
|
607
|
+
if (hc) {
|
|
608
|
+
clearTimeout(hc);
|
|
609
|
+
this.healthChecks.delete(publicKeyHash);
|
|
610
|
+
}
|
|
647
611
|
const existing = this.peers.get(publicKeyHash);
|
|
648
612
|
// If peer streams already exists, do nothing
|
|
649
613
|
if (existing != null) {
|
|
@@ -690,32 +654,7 @@ export class DirectStream extends EventEmitter {
|
|
|
690
654
|
try {
|
|
691
655
|
await pipe(stream, async (source) => {
|
|
692
656
|
for await (const data of source) {
|
|
693
|
-
|
|
694
|
-
if (this.seenCache.has(msgId)) {
|
|
695
|
-
// we got message that WE sent?
|
|
696
|
-
/**
|
|
697
|
-
* Most propobable reason why we arrive here is a race condition/issue
|
|
698
|
-
|
|
699
|
-
┌─┐
|
|
700
|
-
│0│
|
|
701
|
-
└△┘
|
|
702
|
-
┌▽┐
|
|
703
|
-
│1│
|
|
704
|
-
└△┘
|
|
705
|
-
┌▽┐
|
|
706
|
-
│2│
|
|
707
|
-
└─┘
|
|
708
|
-
|
|
709
|
-
from 2s perspective,
|
|
710
|
-
|
|
711
|
-
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
|
|
712
|
-
but two is already connected by onPeerConnected has not been invoked yet, so the hello message gets forwarded,
|
|
713
|
-
and later onPeerConnected gets invoked on 1, and the same message gets resent to 2
|
|
714
|
-
*/
|
|
715
|
-
continue;
|
|
716
|
-
}
|
|
717
|
-
this.seenCache.add(msgId);
|
|
718
|
-
this.processRpc(peerId, peerStreams, data).catch((err) => logger.warn(err));
|
|
657
|
+
this.processRpc(peerId, peerStreams, data).catch(logError);
|
|
719
658
|
}
|
|
720
659
|
});
|
|
721
660
|
}
|
|
@@ -731,10 +670,6 @@ export class DirectStream extends EventEmitter {
|
|
|
731
670
|
* Handles an rpc request from a peer
|
|
732
671
|
*/
|
|
733
672
|
async processRpc(from, peerStreams, message) {
|
|
734
|
-
if (!this.acceptFrom(from)) {
|
|
735
|
-
logger.debug("received message from unacceptable peer %p", from);
|
|
736
|
-
return false;
|
|
737
|
-
}
|
|
738
673
|
// logger.debug("rpc from " + from + ", " + this.peerIdStr);
|
|
739
674
|
if (message.length > 0) {
|
|
740
675
|
// logger.debug("messages from " + from);
|
|
@@ -747,18 +682,21 @@ export class DirectStream extends EventEmitter {
|
|
|
747
682
|
logger.error(err);
|
|
748
683
|
}
|
|
749
684
|
})
|
|
750
|
-
.catch(
|
|
685
|
+
.catch(logError);
|
|
751
686
|
}
|
|
752
687
|
return true;
|
|
753
688
|
}
|
|
689
|
+
async modifySeenCache(message, getIdFn = getMsgId) {
|
|
690
|
+
const msgId = await getIdFn(message);
|
|
691
|
+
const seen = this.seenCache.get(msgId);
|
|
692
|
+
this.seenCache.add(msgId, seen ? seen + 1 : 1);
|
|
693
|
+
return seen || 0;
|
|
694
|
+
}
|
|
754
695
|
/**
|
|
755
696
|
* Handles a message from a peer
|
|
756
697
|
*/
|
|
757
698
|
async processMessage(from, peerStream, msg) {
|
|
758
|
-
if (!
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
if (this.components.peerId.equals(from) && !this.emitSelf) {
|
|
699
|
+
if (!this.started) {
|
|
762
700
|
return;
|
|
763
701
|
}
|
|
764
702
|
// Ensure the message is valid before processing it
|
|
@@ -767,208 +705,226 @@ export class DirectStream extends EventEmitter {
|
|
|
767
705
|
detail: message
|
|
768
706
|
}));
|
|
769
707
|
if (message instanceof DataMessage) {
|
|
770
|
-
await this
|
|
708
|
+
// DONT await this since it might introduce a dead-lock
|
|
709
|
+
this._onDataMessage(from, peerStream, msg, message).catch(logError);
|
|
771
710
|
}
|
|
772
|
-
else
|
|
773
|
-
|
|
711
|
+
else {
|
|
712
|
+
if (message instanceof ACK) {
|
|
713
|
+
this.onAck(from, peerStream, msg, message).catch(logError);
|
|
714
|
+
}
|
|
715
|
+
else if (message instanceof Goodbye) {
|
|
716
|
+
this.onGoodBye(from, peerStream, msg, message).catch(logError);
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
throw new Error("Unsupported message type");
|
|
720
|
+
}
|
|
774
721
|
}
|
|
775
|
-
|
|
776
|
-
|
|
722
|
+
}
|
|
723
|
+
shouldIgnore(message, seenBefore) {
|
|
724
|
+
const fromMe = message.header.signatures?.publicKeys.find((x) => x.equals(this.publicKey));
|
|
725
|
+
if (fromMe) {
|
|
726
|
+
return true;
|
|
727
|
+
}
|
|
728
|
+
if ((seenBefore > 0 &&
|
|
729
|
+
message.header.mode instanceof SeekDelivery === false) ||
|
|
730
|
+
(message.header.mode instanceof SeekDelivery &&
|
|
731
|
+
seenBefore >= message.header.mode.redundancy)) {
|
|
732
|
+
return true;
|
|
733
|
+
}
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
async onDataMessage(from, peerStream, message, seenBefore) {
|
|
737
|
+
if (this.shouldIgnore(message, seenBefore)) {
|
|
738
|
+
return false;
|
|
777
739
|
}
|
|
778
|
-
|
|
779
|
-
|
|
740
|
+
let isForMe = false;
|
|
741
|
+
if (message.header.mode instanceof AnyWhere) {
|
|
742
|
+
isForMe = true;
|
|
780
743
|
}
|
|
781
744
|
else {
|
|
782
|
-
|
|
745
|
+
const isFromSelf = this.publicKey.equals(from);
|
|
746
|
+
if (!isFromSelf) {
|
|
747
|
+
isForMe =
|
|
748
|
+
message.header.mode.to == null ||
|
|
749
|
+
message.header.mode.to.find((x) => x === this.publicKeyHash) != null;
|
|
750
|
+
}
|
|
783
751
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
!(await message.verify(this.signaturePolicy === "StictSign"))) {
|
|
793
|
-
// we don't verify messages we don't dispatch because of the performance penalty // TODO add opts for this
|
|
794
|
-
logger.warn("Recieved message with invalid signature or timestamp");
|
|
795
|
-
return false;
|
|
796
|
-
}
|
|
752
|
+
if (isForMe) {
|
|
753
|
+
if ((await this.maybeVerifyMessage(message)) === false) {
|
|
754
|
+
// we don't verify messages we don't dispatch because of the performance penalty // TODO add opts for this
|
|
755
|
+
logger.warn("Recieved message with invalid signature or timestamp");
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
await this.acknowledgeMessage(peerStream, message, seenBefore);
|
|
759
|
+
if (seenBefore === 0 && message.data) {
|
|
797
760
|
this.dispatchEvent(new CustomEvent("data", {
|
|
798
761
|
detail: message
|
|
799
762
|
}));
|
|
800
763
|
}
|
|
801
|
-
|
|
764
|
+
}
|
|
765
|
+
if (message.header.mode instanceof SilentDelivery ||
|
|
766
|
+
message.header.mode instanceof AcknowledgeDelivery) {
|
|
767
|
+
if (message.header.mode.to &&
|
|
768
|
+
message.header.mode.to.length === 1 &&
|
|
769
|
+
message.header.mode.to[0] === this.publicKeyHash) {
|
|
802
770
|
// dont forward this message anymore because it was meant ONLY for me
|
|
803
771
|
return true;
|
|
804
772
|
}
|
|
805
773
|
}
|
|
806
774
|
// Forward
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
if (!sender) {
|
|
820
|
-
logger.warn("Recieved hello without sender");
|
|
821
|
-
return false;
|
|
822
|
-
}
|
|
823
|
-
const signatures = message.signatures;
|
|
824
|
-
for (let i = 0; i < signatures.signatures.length - 1; i++) {
|
|
825
|
-
this.addRouteConnection(signatures.signatures[i].publicKey, signatures.signatures[i + 1].publicKey, message.networkInfo.pingLatencies[i]);
|
|
826
|
-
}
|
|
827
|
-
message.networkInfo.pingLatencies.push(peerStream.pingLatency || 4294967295); // TODO don't propagate if latency is high?
|
|
828
|
-
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)
|
|
829
|
-
let hellos = this.helloMap.get(sender);
|
|
830
|
-
if (!hellos) {
|
|
831
|
-
hellos = new Map();
|
|
832
|
-
this.helloMap.set(sender, hellos);
|
|
833
|
-
}
|
|
834
|
-
this.multiaddrsMap.set(sender, message.multiaddrs);
|
|
835
|
-
const helloSignaturHash = await message.signatures.hashPublicKeys();
|
|
836
|
-
const existingHello = hellos.get(helloSignaturHash);
|
|
837
|
-
if (existingHello) {
|
|
838
|
-
if (existingHello.header.expires < message.header.expires) {
|
|
839
|
-
hellos.set(helloSignaturHash, message);
|
|
775
|
+
if (message.header.mode instanceof SeekDelivery || seenBefore === 0) {
|
|
776
|
+
// DONT await this since it might introduce a dead-lock
|
|
777
|
+
if (message.header.mode instanceof SeekDelivery) {
|
|
778
|
+
if (seenBefore < message.header.mode.redundancy) {
|
|
779
|
+
const to = [...this.peers.values()].filter((x) => !message.header.signatures?.publicKeys.find((y) => y.equals(x.publicKey)) && x != peerStream);
|
|
780
|
+
if (to.length > 0) {
|
|
781
|
+
this.relayMessage(from, message, to);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
this.relayMessage(from, message);
|
|
840
787
|
}
|
|
841
788
|
}
|
|
842
|
-
|
|
843
|
-
|
|
789
|
+
}
|
|
790
|
+
async maybeVerifyMessage(message) {
|
|
791
|
+
return message.verify(this.signaturePolicy === "StictSign");
|
|
792
|
+
}
|
|
793
|
+
async acknowledgeMessage(peerStream, message, seenBefore) {
|
|
794
|
+
if ((message.header.mode instanceof SeekDelivery ||
|
|
795
|
+
message.header.mode instanceof AcknowledgeDelivery) &&
|
|
796
|
+
seenBefore < message.header.mode.redundancy) {
|
|
797
|
+
await this.publishMessage(this.publicKey, await new ACK({
|
|
798
|
+
messageIdToAcknowledge: message.id,
|
|
799
|
+
seenCounter: seenBefore,
|
|
800
|
+
// TODO only give origin info to peers we want to connect to us
|
|
801
|
+
header: new MessageHeader({
|
|
802
|
+
mode: new TracedDelivery(message.header.signatures.publicKeys.map((x) => x.hashcode())),
|
|
803
|
+
// include our origin if message is SeekDelivery and we have not recently pruned a connection to this peer
|
|
804
|
+
origin: message.header.mode instanceof SeekDelivery &&
|
|
805
|
+
!message.header.signatures.publicKeys.find((x) => this.prunedConnectionsCache?.has(x.hashcode()))
|
|
806
|
+
? new MultiAddrinfo(this.components.addressManager
|
|
807
|
+
.getAddresses()
|
|
808
|
+
.map((x) => x.toString()))
|
|
809
|
+
: undefined
|
|
810
|
+
})
|
|
811
|
+
}).sign(this.sign), [peerStream]);
|
|
844
812
|
}
|
|
845
|
-
// Forward
|
|
846
|
-
await this.relayMessage(from, message);
|
|
847
|
-
return true;
|
|
848
813
|
}
|
|
849
|
-
async
|
|
850
|
-
|
|
851
|
-
|
|
814
|
+
async _onDataMessage(from, peerStream, messageBytes, message) {
|
|
815
|
+
const seenBefore = await this.modifySeenCache(messageBytes instanceof Uint8ArrayList
|
|
816
|
+
? messageBytes.subarray()
|
|
817
|
+
: messageBytes);
|
|
818
|
+
return this.onDataMessage(from, peerStream, message, seenBefore);
|
|
819
|
+
}
|
|
820
|
+
async onAck(publicKey, peerStream, messageBytes, message) {
|
|
821
|
+
const seenBefore = await this.modifySeenCache(messageBytes instanceof Uint8Array
|
|
822
|
+
? messageBytes
|
|
823
|
+
: messageBytes.subarray(), (bytes) => sha256Base64(bytes));
|
|
824
|
+
if (seenBefore > 1) {
|
|
825
|
+
logger.debug("Received message already seen of type: " + message.constructor.name);
|
|
852
826
|
return false;
|
|
853
827
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
logger.warn("Recieved hello without sender");
|
|
828
|
+
if (!(await message.verify(true))) {
|
|
829
|
+
logger.warn(`Recieved ACK message that did not verify`);
|
|
857
830
|
return false;
|
|
858
831
|
}
|
|
859
|
-
const
|
|
860
|
-
const
|
|
861
|
-
|
|
862
|
-
|
|
832
|
+
const messageIdString = toBase64(message.messageIdToAcknowledge);
|
|
833
|
+
const myIndex = message.header.mode.trace.findIndex((x) => x === this.publicKeyHash);
|
|
834
|
+
const next = message.header.mode.trace[myIndex - 1];
|
|
835
|
+
const nextStream = next ? this.peers.get(next) : undefined;
|
|
836
|
+
this._ackCallbacks
|
|
837
|
+
.get(messageIdString)
|
|
838
|
+
?.callback(message, peerStream, nextStream);
|
|
839
|
+
// relay ACK ?
|
|
840
|
+
// send exactly backwards same route we got this message
|
|
841
|
+
if (nextStream) {
|
|
842
|
+
await this.publishMessage(this.publicKey, message, [nextStream], true);
|
|
863
843
|
}
|
|
864
844
|
else {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
this.addRouteConnection(
|
|
869
|
-
signatures.signatures[i].publicKey,
|
|
870
|
-
signatures.signatures[i + 1].publicKey
|
|
871
|
-
);
|
|
845
|
+
// if origin exist (we can connect to remote peer) && we have autodialer turned on
|
|
846
|
+
if (message.header.origin && this.connectionManagerOptions.dialer) {
|
|
847
|
+
this.maybeConnectDirectly(message.header.signatures.publicKeys[0].hashcode(), message.header.origin);
|
|
872
848
|
}
|
|
873
|
-
*/
|
|
874
|
-
//let neighbour = message.trace[1] || this.peerIdStr;
|
|
875
|
-
this.removeRouteConnection(signatures.signatures[0].publicKey, signatures.signatures[1].publicKey || this.publicKey);
|
|
876
|
-
const relayToPeers = [];
|
|
877
|
-
for (const stream of this.peers.values()) {
|
|
878
|
-
if (stream.peerId.equals(from)) {
|
|
879
|
-
continue;
|
|
880
|
-
}
|
|
881
|
-
relayToPeers.push(stream);
|
|
882
|
-
}
|
|
883
|
-
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)
|
|
884
|
-
const hellos = this.helloMap.get(sender);
|
|
885
|
-
if (hellos) {
|
|
886
|
-
const helloSignaturHash = await message.signatures.hashPublicKeys();
|
|
887
|
-
hellos.delete(helloSignaturHash);
|
|
888
|
-
}
|
|
889
|
-
// Forward
|
|
890
|
-
await this.relayMessage(from, message);
|
|
891
849
|
}
|
|
892
|
-
return true;
|
|
893
850
|
}
|
|
894
|
-
async
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
peerStream.pingJob?.resolve();
|
|
851
|
+
async onGoodBye(publicKey, peerStream, messageBytes, message) {
|
|
852
|
+
const seenBefore = await this.modifySeenCache(messageBytes instanceof Uint8Array
|
|
853
|
+
? messageBytes
|
|
854
|
+
: messageBytes.subarray());
|
|
855
|
+
if (seenBefore > 0) {
|
|
856
|
+
logger.debug("Received message already seen of type: " + message.constructor.name);
|
|
857
|
+
return;
|
|
902
858
|
}
|
|
903
|
-
|
|
904
|
-
|
|
859
|
+
if (!(await message.verify(true))) {
|
|
860
|
+
logger.warn(`Recieved ACK message that did not verify`);
|
|
861
|
+
return false;
|
|
905
862
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
const end = +new Date();
|
|
917
|
-
clearTimeout(timeout);
|
|
918
|
-
// TODO what happens if a peer send a ping back then leaves? Any problems?
|
|
919
|
-
const latency = end - start;
|
|
920
|
-
stream.pingLatency = latency;
|
|
921
|
-
this.addRouteConnection(this.publicKey, stream.publicKey, latency);
|
|
922
|
-
resolve(undefined);
|
|
923
|
-
};
|
|
924
|
-
stream.pingJob = {
|
|
925
|
-
resolve: resolver,
|
|
926
|
-
abort: () => {
|
|
927
|
-
clearTimeout(timeout);
|
|
928
|
-
resolve(undefined);
|
|
863
|
+
const filteredLeaving = message.leaving.filter((x) => this.routes.hasTarget(x));
|
|
864
|
+
if (filteredLeaving.length > 0) {
|
|
865
|
+
this.publish(new Uint8Array(0), {
|
|
866
|
+
mode: new SeekDelivery({
|
|
867
|
+
to: filteredLeaving,
|
|
868
|
+
redundancy: DEFAULT_SEEK_MESSAGE_REDUDANCY
|
|
869
|
+
})
|
|
870
|
+
}).catch((e) => {
|
|
871
|
+
if (e instanceof TimeoutError || e instanceof AbortError) {
|
|
872
|
+
// peer left or closed
|
|
929
873
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
874
|
+
else {
|
|
875
|
+
throw e;
|
|
876
|
+
}
|
|
877
|
+
}); // this will remove the target if it is still not reable
|
|
878
|
+
}
|
|
879
|
+
for (const target of message.leaving) {
|
|
880
|
+
// relay message to every one who previously talked to this peer
|
|
881
|
+
const dependent = this.routes.getDependent(target);
|
|
882
|
+
message.header.mode.to = [...message.header.mode.to, ...dependent];
|
|
883
|
+
message.header.mode.to = message.header.mode.to.filter((x) => x !== this.publicKeyHash);
|
|
884
|
+
if (message.header.mode.to.length > 0) {
|
|
885
|
+
await this.publishMessage(publicKey, message, undefined, true);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
943
888
|
}
|
|
944
889
|
async createMessage(data, options) {
|
|
945
890
|
// dispatch the event if we are interested
|
|
946
|
-
let
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
to instanceof PublicSignKey
|
|
958
|
-
? to.hashcode()
|
|
959
|
-
: typeof to === "string"
|
|
960
|
-
? to
|
|
961
|
-
: getPublicKeyFromPeerId(to).hashcode();
|
|
891
|
+
let mode = options.mode
|
|
892
|
+
? options.mode
|
|
893
|
+
: new SilentDelivery({
|
|
894
|
+
to: options.to,
|
|
895
|
+
redundancy: DEFAULT_SILENT_MESSAGE_REDUDANCY
|
|
896
|
+
});
|
|
897
|
+
if (mode instanceof AcknowledgeDelivery ||
|
|
898
|
+
mode instanceof SilentDelivery ||
|
|
899
|
+
mode instanceof SeekDelivery) {
|
|
900
|
+
if (mode.to?.find((x) => x === this.publicKeyHash)) {
|
|
901
|
+
mode.to = mode.to.filter((x) => x !== this.publicKeyHash);
|
|
962
902
|
}
|
|
963
903
|
}
|
|
964
|
-
|
|
965
|
-
|
|
904
|
+
if (mode instanceof AcknowledgeDelivery || mode instanceof SilentDelivery) {
|
|
905
|
+
const now = +new Date();
|
|
906
|
+
for (const hash of mode.to) {
|
|
907
|
+
const neighbourRoutes = this.routes.routes
|
|
908
|
+
.get(this.publicKeyHash)
|
|
909
|
+
?.get(hash);
|
|
910
|
+
if (!neighbourRoutes ||
|
|
911
|
+
now - neighbourRoutes.session >
|
|
912
|
+
neighbourRoutes.list.length * this.routeSeekInterval) {
|
|
913
|
+
mode = new SeekDelivery({
|
|
914
|
+
to: mode.to,
|
|
915
|
+
redundancy: DEFAULT_SEEK_MESSAGE_REDUDANCY
|
|
916
|
+
});
|
|
917
|
+
break;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
966
920
|
}
|
|
967
921
|
const message = new DataMessage({
|
|
968
922
|
data: data instanceof Uint8ArrayList ? data.subarray() : data,
|
|
969
|
-
|
|
923
|
+
header: new MessageHeader({ mode })
|
|
970
924
|
});
|
|
971
|
-
if (this.signaturePolicy === "StictSign"
|
|
925
|
+
if (this.signaturePolicy === "StictSign" ||
|
|
926
|
+
mode instanceof SeekDelivery ||
|
|
927
|
+
mode instanceof AcknowledgeDelivery) {
|
|
972
928
|
await message.sign(this.sign);
|
|
973
929
|
}
|
|
974
930
|
return message;
|
|
@@ -976,122 +932,195 @@ export class DirectStream extends EventEmitter {
|
|
|
976
932
|
/**
|
|
977
933
|
* Publishes messages to all peers
|
|
978
934
|
*/
|
|
979
|
-
async publish(data, options
|
|
935
|
+
async publish(data, options = {
|
|
936
|
+
mode: new SeekDelivery({ redundancy: DEFAULT_SEEK_MESSAGE_REDUDANCY })
|
|
937
|
+
}) {
|
|
980
938
|
if (!this.started) {
|
|
981
939
|
throw new Error("Not started");
|
|
982
940
|
}
|
|
983
941
|
const message = await this.createMessage(data, options);
|
|
984
|
-
|
|
985
|
-
super.dispatchEvent(new CustomEvent("data", {
|
|
986
|
-
detail: message
|
|
987
|
-
}));
|
|
988
|
-
}
|
|
989
|
-
// send to all the other peers
|
|
990
|
-
await this.publishMessage(this.components.peerId, message, undefined);
|
|
942
|
+
await this.publishMessage(this.publicKey, message, undefined);
|
|
991
943
|
return message.id;
|
|
992
944
|
}
|
|
993
|
-
async hello(data) {
|
|
994
|
-
if (!this.started) {
|
|
995
|
-
return;
|
|
996
|
-
}
|
|
997
|
-
// send to all the other peers
|
|
998
|
-
await this.publishMessage(this.components.peerId, await new Hello({
|
|
999
|
-
multiaddrs: this.components.addressManager
|
|
1000
|
-
.getAddresses()
|
|
1001
|
-
.map((x) => x.toString()),
|
|
1002
|
-
data
|
|
1003
|
-
}).sign(this.sign.bind(this)));
|
|
1004
|
-
}
|
|
1005
945
|
async relayMessage(from, message, to) {
|
|
1006
946
|
if (this.canRelayMessage) {
|
|
947
|
+
if (message instanceof DataMessage) {
|
|
948
|
+
if (message.header.mode instanceof AcknowledgeDelivery ||
|
|
949
|
+
message.header.mode instanceof SeekDelivery) {
|
|
950
|
+
await message.sign(this.sign);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
1007
953
|
return this.publishMessage(from, message, to, true);
|
|
1008
954
|
}
|
|
1009
955
|
else {
|
|
1010
|
-
logger.debug("
|
|
956
|
+
logger.debug("Received a message to relay but canRelayMessage is false");
|
|
1011
957
|
}
|
|
1012
958
|
}
|
|
1013
|
-
async
|
|
1014
|
-
if (message instanceof
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
959
|
+
async createDeliveryPromise(from, message, relayed) {
|
|
960
|
+
if (message.header.mode instanceof AnyWhere) {
|
|
961
|
+
return { promise: Promise.resolve() };
|
|
962
|
+
}
|
|
963
|
+
const idString = toBase64(message.id);
|
|
964
|
+
const existing = this._ackCallbacks.get(idString);
|
|
965
|
+
if (existing) {
|
|
966
|
+
return { promise: existing.promise };
|
|
967
|
+
}
|
|
968
|
+
const deliveryDeferredPromise = pDefer();
|
|
969
|
+
const fastestNodesReached = new Map();
|
|
970
|
+
const messageToSet = new Set();
|
|
971
|
+
if (message.header.mode.to) {
|
|
972
|
+
for (const to of message.header.mode.to) {
|
|
973
|
+
if (to === from.hashcode()) {
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
messageToSet.add(to);
|
|
977
|
+
if (!relayed && !this.healthChecks.has(to)) {
|
|
978
|
+
this.healthChecks.set(to, setTimeout(() => {
|
|
979
|
+
this.removeRouteConnection(to, false);
|
|
980
|
+
}, this.seekTimeout + 5000));
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (messageToSet.size === 0) {
|
|
985
|
+
deliveryDeferredPromise.resolve(); // we dont know how many answer to expect, just resolve immediately
|
|
986
|
+
}
|
|
987
|
+
const willGetAllAcknowledgements = !relayed; // Only the origin will get all acks
|
|
988
|
+
// Expected to receive at least 'filterMessageForSeenCounter' acknowledgements from each peer
|
|
989
|
+
const filterMessageForSeenCounter = relayed
|
|
990
|
+
? undefined
|
|
991
|
+
: message.header.mode instanceof SeekDelivery
|
|
992
|
+
? Math.min(this.peers.size, message.header.mode.redundancy)
|
|
993
|
+
: 1; /* message.deliveryMode instanceof SeekDelivery ? Math.min(this.peers.size - (relayed ? 1 : 0), message.deliveryMode.redundancy) : 1 */
|
|
994
|
+
const finalize = () => {
|
|
995
|
+
this._ackCallbacks.delete(idString);
|
|
996
|
+
if (message.header.mode instanceof SeekDelivery) {
|
|
997
|
+
this.routes.commitPendingRouteConnection(session);
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
const uniqueAcks = new Set();
|
|
1001
|
+
const session = +new Date();
|
|
1002
|
+
const timeout = setTimeout(async () => {
|
|
1003
|
+
let hasAll = true;
|
|
1004
|
+
finalize();
|
|
1005
|
+
// peer not reachable (?)!
|
|
1006
|
+
for (const to of messageToSet) {
|
|
1007
|
+
let foundNode = false;
|
|
1008
|
+
if (fastestNodesReached.has(to)) {
|
|
1009
|
+
foundNode = true;
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
1012
|
+
if (!foundNode && !relayed) {
|
|
1013
|
+
hasAll = false;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
if (!hasAll && willGetAllAcknowledgements) {
|
|
1017
|
+
deliveryDeferredPromise.reject(new TimeoutError(`${this.publicKeyHash} Failed to get message ${idString} ${filterMessageForSeenCounter} ${[
|
|
1018
|
+
...messageToSet
|
|
1019
|
+
]} delivery acknowledges from all nodes (${fastestNodesReached.size}/${messageToSet.size}). Mode: ${message.header.mode.constructor.name}`));
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
deliveryDeferredPromise.resolve();
|
|
1023
|
+
}
|
|
1024
|
+
}, this.seekTimeout);
|
|
1025
|
+
this._ackCallbacks.set(idString, {
|
|
1026
|
+
promise: deliveryDeferredPromise.promise,
|
|
1027
|
+
callback: (ack, messageThrough, messageFrom) => {
|
|
1028
|
+
const messageTarget = ack.header.signatures.publicKeys[0];
|
|
1029
|
+
const messageTargetHash = messageTarget.hashcode();
|
|
1030
|
+
const seenCounter = ack.seenCounter;
|
|
1031
|
+
// remove the automatic removal of route timeout since we have observed lifesigns of a peer
|
|
1032
|
+
const timer = this.healthChecks.get(messageTargetHash);
|
|
1033
|
+
clearTimeout(timer);
|
|
1034
|
+
this.healthChecks.delete(messageTargetHash);
|
|
1035
|
+
// if the target is not inside the original message to, we still ad the target to our routes
|
|
1036
|
+
// this because a relay might modify the 'to' list and we might receive more answers than initially set
|
|
1037
|
+
if (message.header.mode instanceof SeekDelivery) {
|
|
1038
|
+
this.addRouteConnection(messageFrom?.publicKey.hashcode() || this.publicKeyHash, messageThrough.publicKey.hashcode(), messageTarget, seenCounter, session, true); // we assume the seenCounter = distance. The more the message has been seen by the target the longer the path is to the target
|
|
1039
|
+
}
|
|
1040
|
+
if (messageToSet.has(messageTargetHash)) {
|
|
1041
|
+
// Only keep track of relevant acks
|
|
1042
|
+
if (filterMessageForSeenCounter == null ||
|
|
1043
|
+
seenCounter < filterMessageForSeenCounter) {
|
|
1044
|
+
// TODO set limit correctly
|
|
1045
|
+
if (seenCounter < MAX_ROUTE_DISTANCE) {
|
|
1046
|
+
let arr = fastestNodesReached.get(messageTargetHash);
|
|
1047
|
+
if (!arr) {
|
|
1048
|
+
arr = [];
|
|
1049
|
+
fastestNodesReached.set(messageTargetHash, arr);
|
|
1062
1050
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
missingPathsFor.push(to);
|
|
1051
|
+
arr.push(seenCounter);
|
|
1052
|
+
uniqueAcks.add(messageTargetHash + seenCounter);
|
|
1066
1053
|
}
|
|
1067
1054
|
}
|
|
1068
|
-
// we can't find path, send message to all peers
|
|
1069
|
-
fanoutMap.clear();
|
|
1070
|
-
break;
|
|
1071
1055
|
}
|
|
1072
|
-
//
|
|
1073
|
-
|
|
1074
|
-
if (
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1056
|
+
// This if clause should never enter for relayed connections, since we don't
|
|
1057
|
+
// know how many ACKs we will get
|
|
1058
|
+
if (filterMessageForSeenCounter != null &&
|
|
1059
|
+
uniqueAcks.size >= messageToSet.size * filterMessageForSeenCounter) {
|
|
1060
|
+
if (messageToSet.size > 0) {
|
|
1061
|
+
// 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
|
|
1062
|
+
// only remove callback function if we actually expected a expected amount of responses
|
|
1063
|
+
clearTimeout(timeout);
|
|
1064
|
+
finalize();
|
|
1065
|
+
}
|
|
1066
|
+
deliveryDeferredPromise.resolve();
|
|
1067
|
+
}
|
|
1068
|
+
},
|
|
1069
|
+
timeout
|
|
1070
|
+
});
|
|
1071
|
+
return deliveryDeferredPromise;
|
|
1072
|
+
}
|
|
1073
|
+
async publishMessage(from, message, to, relayed) {
|
|
1074
|
+
let delivereyPromise = undefined;
|
|
1075
|
+
if ((!message.header.signatures ||
|
|
1076
|
+
message.header.signatures.publicKeys.length === 0) &&
|
|
1077
|
+
message instanceof DataMessage &&
|
|
1078
|
+
message.header.mode instanceof SilentDelivery === false) {
|
|
1079
|
+
throw new Error("Missing signature");
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Logic for handling acknowledge messages when we receive them (later)
|
|
1083
|
+
*/
|
|
1084
|
+
if (message instanceof DataMessage &&
|
|
1085
|
+
message.header.mode instanceof SeekDelivery &&
|
|
1086
|
+
!relayed) {
|
|
1087
|
+
to = this.peers; // seek delivery will not work unless we try all possible paths
|
|
1088
|
+
}
|
|
1089
|
+
if (message instanceof AcknowledgeDelivery) {
|
|
1090
|
+
to = undefined;
|
|
1091
|
+
}
|
|
1092
|
+
if (message instanceof DataMessage &&
|
|
1093
|
+
(message.header.mode instanceof SeekDelivery ||
|
|
1094
|
+
message.header.mode instanceof AcknowledgeDelivery)) {
|
|
1095
|
+
delivereyPromise = (await this.createDeliveryPromise(from, message, relayed)).promise;
|
|
1096
|
+
}
|
|
1097
|
+
const bytes = message.bytes();
|
|
1098
|
+
if (!relayed) {
|
|
1099
|
+
const bytesArray = bytes instanceof Uint8Array ? bytes : bytes.subarray();
|
|
1100
|
+
await this.modifySeenCache(bytesArray);
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* For non SEEKing message delivery modes, use routing
|
|
1104
|
+
*/
|
|
1105
|
+
if (message instanceof DataMessage) {
|
|
1106
|
+
if ((message.header.mode instanceof AcknowledgeDelivery ||
|
|
1107
|
+
message.header.mode instanceof SilentDelivery) &&
|
|
1108
|
+
!to &&
|
|
1109
|
+
message.header.mode.to) {
|
|
1110
|
+
const fanout = this.routes.getFanout(from, message.header.mode.to, message.header.mode.redundancy);
|
|
1111
|
+
if (fanout) {
|
|
1112
|
+
if (fanout.size > 0) {
|
|
1113
|
+
for (const [neighbour, _distantPeers] of fanout) {
|
|
1086
1114
|
const stream = this.peers.get(neighbour);
|
|
1087
1115
|
stream?.waitForWrite(bytes).catch((e) => {
|
|
1088
1116
|
logger.error("Failed to publish message: " + e.message);
|
|
1089
1117
|
});
|
|
1090
1118
|
}
|
|
1091
|
-
return; // we are done sending the message in all direction with updates 'to' lists
|
|
1119
|
+
return delivereyPromise; // we are done sending the message in all direction with updates 'to' lists
|
|
1092
1120
|
}
|
|
1093
|
-
return;
|
|
1121
|
+
return; // we defintely that we should not forward the message anywhere
|
|
1094
1122
|
}
|
|
1123
|
+
return;
|
|
1095
1124
|
// else send to all (fallthrough to code below)
|
|
1096
1125
|
}
|
|
1097
1126
|
}
|
|
@@ -1100,108 +1129,64 @@ export class DirectStream extends EventEmitter {
|
|
|
1100
1129
|
if (peers == null ||
|
|
1101
1130
|
(Array.isArray(peers) && peers.length === 0) ||
|
|
1102
1131
|
(peers instanceof Map && peers.size === 0)) {
|
|
1103
|
-
logger.debug("
|
|
1132
|
+
logger.debug("No peers to send to");
|
|
1104
1133
|
return;
|
|
1105
1134
|
}
|
|
1106
|
-
const bytes = message.bytes();
|
|
1107
1135
|
let sentOnce = false;
|
|
1108
1136
|
for (const stream of peers.values()) {
|
|
1109
1137
|
const id = stream;
|
|
1110
|
-
if (id.
|
|
1138
|
+
if (id.publicKey.equals(from)) {
|
|
1111
1139
|
continue;
|
|
1112
1140
|
}
|
|
1113
|
-
|
|
1114
|
-
sentOnce = true;
|
|
1115
|
-
if (!relayed) {
|
|
1116
|
-
// if relayed = true, we have already added it to seenCache
|
|
1117
|
-
const msgId = await getMsgId(bytes);
|
|
1118
|
-
this.seenCache.add(msgId);
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1141
|
+
sentOnce = true;
|
|
1121
1142
|
id.waitForWrite(bytes).catch((e) => {
|
|
1122
1143
|
logger.error("Failed to publish message: " + e.message);
|
|
1123
1144
|
});
|
|
1124
1145
|
}
|
|
1125
|
-
if (!sentOnce
|
|
1126
|
-
|
|
1146
|
+
if (!sentOnce) {
|
|
1147
|
+
if (!relayed) {
|
|
1148
|
+
throw new Error("Message did not have any valid receivers");
|
|
1149
|
+
}
|
|
1127
1150
|
}
|
|
1151
|
+
return delivereyPromise;
|
|
1128
1152
|
}
|
|
1129
|
-
async maybeConnectDirectly(
|
|
1130
|
-
if (
|
|
1131
|
-
return;
|
|
1132
|
-
}
|
|
1133
|
-
const toHash = path[path.length - 1];
|
|
1134
|
-
if (this.peers.has(toHash)) {
|
|
1153
|
+
async maybeConnectDirectly(toHash, origin) {
|
|
1154
|
+
if (this.peers.has(toHash) || this.prunedConnectionsCache?.has(toHash)) {
|
|
1135
1155
|
return; // TODO, is this expected, or are we to dial more addresses?
|
|
1136
1156
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
this.recentDials.
|
|
1140
|
-
|
|
1157
|
+
const addresses = origin.multiaddrs
|
|
1158
|
+
.filter((x) => {
|
|
1159
|
+
const ret = !this.recentDials.has(x);
|
|
1160
|
+
this.recentDials.add(x);
|
|
1161
|
+
return ret;
|
|
1162
|
+
})
|
|
1163
|
+
.map((x) => multiaddr(x));
|
|
1164
|
+
if (addresses.length > 0) {
|
|
1141
1165
|
try {
|
|
1142
|
-
|
|
1143
|
-
await this.components.connectionManager.openConnection(addrs.map((x) => multiaddr(x)));
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1166
|
+
await this.components.connectionManager.openConnection(addresses);
|
|
1146
1167
|
}
|
|
1147
1168
|
catch (error) {
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
const nextToHash = path[path.length - 2];
|
|
1153
|
-
const routeKey = nextToHash + toHash;
|
|
1154
|
-
if (!this.recentDials.has(routeKey)) {
|
|
1155
|
-
this.recentDials.add(routeKey);
|
|
1156
|
-
const to = this.peerKeyHashToPublicKey.get(toHash);
|
|
1157
|
-
const toPeerId = await to.toPeerId();
|
|
1158
|
-
const addrs = this.multiaddrsMap.get(path[path.length - 2]);
|
|
1159
|
-
if (addrs && addrs.length > 0) {
|
|
1160
|
-
const addressesToDial = addrs.sort((a, b) => {
|
|
1161
|
-
if (a.includes("/wss/")) {
|
|
1162
|
-
if (b.includes("/wss/")) {
|
|
1163
|
-
return 0;
|
|
1164
|
-
}
|
|
1165
|
-
return -1;
|
|
1166
|
-
}
|
|
1167
|
-
if (a.includes("/ws/")) {
|
|
1168
|
-
if (b.includes("/ws/")) {
|
|
1169
|
-
return 0;
|
|
1170
|
-
}
|
|
1171
|
-
if (b.includes("/wss/")) {
|
|
1172
|
-
return 1;
|
|
1173
|
-
}
|
|
1174
|
-
return -1;
|
|
1175
|
-
}
|
|
1176
|
-
return 0;
|
|
1177
|
-
});
|
|
1178
|
-
for (const addr of addressesToDial) {
|
|
1179
|
-
const circuitAddress = multiaddr(addr + "/p2p-circuit/webrtc/p2p/" + toPeerId.toString());
|
|
1180
|
-
try {
|
|
1181
|
-
await this.components.connectionManager.openConnection(circuitAddress);
|
|
1182
|
-
return;
|
|
1183
|
-
}
|
|
1184
|
-
catch (error) {
|
|
1185
|
-
logger.error("Failed to connect directly to: " +
|
|
1186
|
-
circuitAddress.toString() +
|
|
1187
|
-
". " +
|
|
1188
|
-
error?.message);
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1169
|
+
logger.info("Failed to connect directly to: " +
|
|
1170
|
+
JSON.stringify(addresses.map((x) => x.toString())) +
|
|
1171
|
+
". " +
|
|
1172
|
+
error?.message);
|
|
1191
1173
|
}
|
|
1192
1174
|
}
|
|
1193
1175
|
}
|
|
1194
|
-
async waitFor(peer) {
|
|
1176
|
+
async waitFor(peer, options) {
|
|
1195
1177
|
const hash = (peer instanceof PublicSignKey ? peer : getPublicKeyFromPeerId(peer)).hashcode();
|
|
1196
1178
|
try {
|
|
1197
1179
|
await waitFor(() => {
|
|
1198
1180
|
if (!this.peers.has(hash)) {
|
|
1199
1181
|
return false;
|
|
1200
1182
|
}
|
|
1201
|
-
if (!this.routes.
|
|
1183
|
+
if (!this.routes.isReachable(this.publicKeyHash, hash)) {
|
|
1202
1184
|
return false;
|
|
1203
1185
|
}
|
|
1204
1186
|
return true;
|
|
1187
|
+
}, {
|
|
1188
|
+
signal: options?.signal,
|
|
1189
|
+
timeout: 10 * 1000
|
|
1205
1190
|
});
|
|
1206
1191
|
}
|
|
1207
1192
|
catch (error) {
|
|
@@ -1210,11 +1195,15 @@ export class DirectStream extends EventEmitter {
|
|
|
1210
1195
|
" does not exist. Connection exist: " +
|
|
1211
1196
|
this.peers.has(hash) +
|
|
1212
1197
|
". Route exist: " +
|
|
1213
|
-
this.routes.
|
|
1198
|
+
this.routes.isReachable(this.publicKeyHash, hash));
|
|
1214
1199
|
}
|
|
1215
1200
|
const stream = this.peers.get(hash);
|
|
1216
1201
|
try {
|
|
1217
|
-
|
|
1202
|
+
// Dontwait for readlable https://github.com/libp2p/js-libp2p/issues/2321
|
|
1203
|
+
await waitFor(() => /* stream.isReadable && */ stream.isWritable, {
|
|
1204
|
+
signal: options?.signal,
|
|
1205
|
+
timeout: 10 * 1000
|
|
1206
|
+
});
|
|
1218
1207
|
}
|
|
1219
1208
|
catch (error) {
|
|
1220
1209
|
throw new Error("Stream to " +
|
|
@@ -1225,6 +1214,58 @@ export class DirectStream extends EventEmitter {
|
|
|
1225
1214
|
stream.isWritable);
|
|
1226
1215
|
}
|
|
1227
1216
|
}
|
|
1217
|
+
get pending() {
|
|
1218
|
+
return this._ackCallbacks.size > 0;
|
|
1219
|
+
}
|
|
1220
|
+
lastQueuedBytes = 0;
|
|
1221
|
+
// make this into a job? run every few ms
|
|
1222
|
+
maybePruneConnections() {
|
|
1223
|
+
if (this.connectionManagerOptions.pruner) {
|
|
1224
|
+
if (this.connectionManagerOptions.pruner.bandwidth != null) {
|
|
1225
|
+
let usedBandwidth = 0;
|
|
1226
|
+
for (const [_k, v] of this.peers) {
|
|
1227
|
+
usedBandwidth += v.usedBandwidth;
|
|
1228
|
+
}
|
|
1229
|
+
usedBandwidth /= this.peers.size;
|
|
1230
|
+
if (usedBandwidth > this.connectionManagerOptions.pruner.bandwidth) {
|
|
1231
|
+
// prune
|
|
1232
|
+
return this.pruneConnections();
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
else if (this.connectionManagerOptions.pruner.maxBuffer != null) {
|
|
1236
|
+
const queuedBytes = this.getQueuedBytes();
|
|
1237
|
+
if (queuedBytes > this.connectionManagerOptions.pruner.maxBuffer) {
|
|
1238
|
+
// prune
|
|
1239
|
+
return this.pruneConnections();
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return Promise.resolve();
|
|
1244
|
+
}
|
|
1245
|
+
async pruneConnections() {
|
|
1246
|
+
// TODO sort by bandwidth
|
|
1247
|
+
if (this.peers.size <= this.connectionManagerOptions.minConnections) {
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
const sorted = [...this.peers.values()]
|
|
1251
|
+
.sort((x, y) => x.usedBandwidth - y.usedBandwidth)
|
|
1252
|
+
.map((x) => x.publicKey.hashcode());
|
|
1253
|
+
const prunables = this.routes.getPrunable(sorted);
|
|
1254
|
+
if (prunables.length === 0) {
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
const stream = this.peers.get(prunables[0]);
|
|
1258
|
+
this.prunedConnectionsCache.add(stream.publicKey.hashcode());
|
|
1259
|
+
await this.onPeerDisconnected(stream.peerId);
|
|
1260
|
+
return this.components.connectionManager.closeConnections(stream.peerId);
|
|
1261
|
+
}
|
|
1262
|
+
getQueuedBytes() {
|
|
1263
|
+
let sum = 0;
|
|
1264
|
+
for (const peer of this.peers) {
|
|
1265
|
+
sum += peer[1].outboundStream?.readableLength || 0;
|
|
1266
|
+
}
|
|
1267
|
+
return sum;
|
|
1268
|
+
}
|
|
1228
1269
|
}
|
|
1229
1270
|
export const waitForPeers = async (...libs) => {
|
|
1230
1271
|
for (let i = 0; i < libs.length; i++) {
|