@peerbit/stream 2.0.5 → 3.0.0
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 +7 -6
- package/lib/esm/index.js +68 -40
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/metrics.js.map +1 -1
- package/lib/esm/routes.d.ts +26 -18
- package/lib/esm/routes.js +133 -40
- package/lib/esm/routes.js.map +1 -1
- package/package.json +4 -3
- package/src/index.ts +90 -52
- package/src/metrics.ts +0 -2
- package/src/routes.ts +163 -61
package/src/index.ts
CHANGED
|
@@ -38,10 +38,9 @@ import type { TypedEventTarget } from "@libp2p/interface";
|
|
|
38
38
|
export type SignaturePolicy = "StictSign" | "StrictNoSign";
|
|
39
39
|
|
|
40
40
|
import { logger } from "./logger.js";
|
|
41
|
-
|
|
42
41
|
export { logger };
|
|
43
|
-
|
|
44
42
|
import { Cache } from "@peerbit/cache";
|
|
43
|
+
|
|
45
44
|
import type { Libp2pEvents } from "@libp2p/interface";
|
|
46
45
|
import { ready } from "@peerbit/crypto";
|
|
47
46
|
import {
|
|
@@ -104,11 +103,11 @@ type WithTo = {
|
|
|
104
103
|
type WithMode = {
|
|
105
104
|
mode?: SilentDelivery | SeekDelivery | AcknowledgeDelivery | AnyWhere;
|
|
106
105
|
};
|
|
106
|
+
|
|
107
107
|
/**
|
|
108
108
|
* Thin wrapper around a peer's inbound / outbound pubsub streams
|
|
109
109
|
*/
|
|
110
110
|
export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
111
|
-
public counter = 0;
|
|
112
111
|
public readonly peerId: PeerId;
|
|
113
112
|
public readonly publicKey: PublicSignKey;
|
|
114
113
|
public readonly protocol: string;
|
|
@@ -149,7 +148,6 @@ export class PeerStreams extends TypedEventEmitter<PeerStreamEvents> {
|
|
|
149
148
|
this.inboundAbortController = new AbortController();
|
|
150
149
|
this.closed = false;
|
|
151
150
|
this.connId = init.connId;
|
|
152
|
-
this.counter = 1;
|
|
153
151
|
this.usedBandWidthTracker = new MovingAverageTracker();
|
|
154
152
|
}
|
|
155
153
|
|
|
@@ -407,7 +405,6 @@ export abstract class DirectStream<
|
|
|
407
405
|
* if publish should emit to self, if subscribed
|
|
408
406
|
*/
|
|
409
407
|
|
|
410
|
-
public signaturePolicy: SignaturePolicy;
|
|
411
408
|
public queue: Queue;
|
|
412
409
|
public multicodecs: string[];
|
|
413
410
|
public seenCache: Cache<number>;
|
|
@@ -423,6 +420,8 @@ export abstract class DirectStream<
|
|
|
423
420
|
routeSeekInterval: number;
|
|
424
421
|
seekTimeout: number;
|
|
425
422
|
closeController: AbortController;
|
|
423
|
+
session: number;
|
|
424
|
+
|
|
426
425
|
private _ackCallbacks: Map<
|
|
427
426
|
string,
|
|
428
427
|
{
|
|
@@ -434,7 +433,7 @@ export abstract class DirectStream<
|
|
|
434
433
|
) => void;
|
|
435
434
|
timeout: ReturnType<typeof setTimeout>;
|
|
436
435
|
}
|
|
437
|
-
|
|
436
|
+
>;
|
|
438
437
|
|
|
439
438
|
constructor(
|
|
440
439
|
readonly components: DirectStreamComponents,
|
|
@@ -475,7 +474,7 @@ export abstract class DirectStream<
|
|
|
475
474
|
this._onIncomingStream = this._onIncomingStream.bind(this);
|
|
476
475
|
this.onPeerConnected = this.onPeerConnected.bind(this);
|
|
477
476
|
this.onPeerDisconnected = this.onPeerDisconnected.bind(this);
|
|
478
|
-
this.
|
|
477
|
+
this._ackCallbacks = new Map();
|
|
479
478
|
|
|
480
479
|
if (connectionManager === false || connectionManager === null) {
|
|
481
480
|
this.connectionManagerOptions = {
|
|
@@ -527,6 +526,7 @@ export abstract class DirectStream<
|
|
|
527
526
|
return;
|
|
528
527
|
}
|
|
529
528
|
|
|
529
|
+
this.session = +new Date();
|
|
530
530
|
await ready;
|
|
531
531
|
|
|
532
532
|
this.closeController = new AbortController();
|
|
@@ -767,7 +767,8 @@ export abstract class DirectStream<
|
|
|
767
767
|
peerKey.hashcode(),
|
|
768
768
|
peerKey,
|
|
769
769
|
-1,
|
|
770
|
-
+new Date()
|
|
770
|
+
+new Date(),
|
|
771
|
+
-1
|
|
771
772
|
);
|
|
772
773
|
} catch (err: any) {
|
|
773
774
|
logger.error(err);
|
|
@@ -804,7 +805,7 @@ export abstract class DirectStream<
|
|
|
804
805
|
|
|
805
806
|
// Notify network
|
|
806
807
|
const dependent = this.routes.getDependent(peerKeyHash);
|
|
807
|
-
this.removeRouteConnection(peerKeyHash
|
|
808
|
+
this.removeRouteConnection(peerKeyHash);
|
|
808
809
|
|
|
809
810
|
if (dependent.length > 0) {
|
|
810
811
|
await this.publishMessage(
|
|
@@ -812,6 +813,7 @@ export abstract class DirectStream<
|
|
|
812
813
|
await new Goodbye({
|
|
813
814
|
leaving: [peerKeyHash],
|
|
814
815
|
header: new MessageHeader({
|
|
816
|
+
session: this.session,
|
|
815
817
|
mode: new SilentDelivery({ to: dependent, redundancy: 2 })
|
|
816
818
|
})
|
|
817
819
|
}).sign(this.sign)
|
|
@@ -822,10 +824,8 @@ export abstract class DirectStream<
|
|
|
822
824
|
logger.debug("connection ended:" + peerKey.toString());
|
|
823
825
|
}
|
|
824
826
|
|
|
825
|
-
public removeRouteConnection(hash: string,
|
|
826
|
-
const unreachable =
|
|
827
|
-
? this.routes.removeNeighbour(hash)
|
|
828
|
-
: this.routes.removeTarget(hash);
|
|
827
|
+
public removeRouteConnection(hash: string, asNeigh = true) {
|
|
828
|
+
const unreachable = this.routes.remove(hash);
|
|
829
829
|
for (const node of unreachable) {
|
|
830
830
|
this.onPeerUnreachable(node); // TODO types
|
|
831
831
|
this.peerKeyHashToPublicKey.delete(node);
|
|
@@ -837,21 +837,27 @@ export abstract class DirectStream<
|
|
|
837
837
|
neighbour: string,
|
|
838
838
|
target: PublicSignKey,
|
|
839
839
|
distance: number,
|
|
840
|
-
session: number
|
|
840
|
+
session: number,
|
|
841
|
+
remoteSession: number
|
|
841
842
|
) {
|
|
842
843
|
const targetHash = typeof target === "string" ? target : target.hashcode();
|
|
843
|
-
const wasReachable =
|
|
844
|
-
from === this.publicKeyHash
|
|
845
|
-
? this.routes.isReachable(from, targetHash)
|
|
846
|
-
: true;
|
|
847
844
|
|
|
848
|
-
this.routes.add(
|
|
845
|
+
const update = this.routes.add(
|
|
846
|
+
from,
|
|
847
|
+
neighbour,
|
|
848
|
+
targetHash,
|
|
849
|
+
distance,
|
|
850
|
+
session,
|
|
851
|
+
remoteSession
|
|
852
|
+
);
|
|
849
853
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
if (
|
|
853
|
-
|
|
854
|
-
|
|
854
|
+
// second condition is that we don't want to emit 'reachable' events for routes where we act only as a relay
|
|
855
|
+
// in this case, from is != this.publicKeyhash
|
|
856
|
+
if (from === this.publicKeyHash) {
|
|
857
|
+
if (update === "new") {
|
|
858
|
+
this.peerKeyHashToPublicKey.set(target.hashcode(), target);
|
|
859
|
+
this.onPeerReachable(target);
|
|
860
|
+
}
|
|
855
861
|
}
|
|
856
862
|
}
|
|
857
863
|
|
|
@@ -881,6 +887,24 @@ export abstract class DirectStream<
|
|
|
881
887
|
);
|
|
882
888
|
}
|
|
883
889
|
|
|
890
|
+
public updateSession(key: PublicSignKey, session?: number) {
|
|
891
|
+
if (this.routes.updateSession(key.hashcode(), session)) {
|
|
892
|
+
return this.onPeerSession(key, session!);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
public invalidateSession(key: string) {
|
|
896
|
+
this.routes.updateSession(key, undefined);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
public onPeerSession(key: PublicSignKey, session: number) {
|
|
900
|
+
this.dispatchEvent(
|
|
901
|
+
// TODO types
|
|
902
|
+
new CustomEvent("peer:session", {
|
|
903
|
+
detail: key
|
|
904
|
+
})
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
|
|
884
908
|
/**
|
|
885
909
|
* Notifies the router that a peer has been connected
|
|
886
910
|
*/
|
|
@@ -898,7 +922,6 @@ export abstract class DirectStream<
|
|
|
898
922
|
|
|
899
923
|
// If peer streams already exists, do nothing
|
|
900
924
|
if (existing != null) {
|
|
901
|
-
existing.counter += 1;
|
|
902
925
|
existing.connId = connId;
|
|
903
926
|
return existing;
|
|
904
927
|
}
|
|
@@ -915,6 +938,8 @@ export abstract class DirectStream<
|
|
|
915
938
|
});
|
|
916
939
|
|
|
917
940
|
this.peers.set(publicKeyHash, peerStreams);
|
|
941
|
+
this.updateSession(publicKey, -1);
|
|
942
|
+
|
|
918
943
|
peerStreams.addEventListener("close", () => this._removePeer(publicKey), {
|
|
919
944
|
once: true
|
|
920
945
|
});
|
|
@@ -1084,7 +1109,7 @@ export abstract class DirectStream<
|
|
|
1084
1109
|
}
|
|
1085
1110
|
|
|
1086
1111
|
if (isForMe) {
|
|
1087
|
-
if ((await this.
|
|
1112
|
+
if (!(await this.verifyAndProcess(message))) {
|
|
1088
1113
|
// we don't verify messages we don't dispatch because of the performance penalty // TODO add opts for this
|
|
1089
1114
|
logger.warn("Recieved message with invalid signature or timestamp");
|
|
1090
1115
|
return false;
|
|
@@ -1136,8 +1161,19 @@ export abstract class DirectStream<
|
|
|
1136
1161
|
}
|
|
1137
1162
|
}
|
|
1138
1163
|
|
|
1139
|
-
public async
|
|
1140
|
-
|
|
1164
|
+
public async verifyAndProcess(message: Message<any>) {
|
|
1165
|
+
const verified = await message.verify(true);
|
|
1166
|
+
if (!verified) {
|
|
1167
|
+
return false;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
const from = message.header.signatures!.publicKeys[0];
|
|
1171
|
+
if (this.peers.has(from.hashcode())) {
|
|
1172
|
+
// do nothing
|
|
1173
|
+
} else {
|
|
1174
|
+
this.updateSession(from, Number(message.header.session));
|
|
1175
|
+
}
|
|
1176
|
+
return true;
|
|
1141
1177
|
}
|
|
1142
1178
|
|
|
1143
1179
|
async acknowledgeMessage(
|
|
@@ -1145,6 +1181,12 @@ export abstract class DirectStream<
|
|
|
1145
1181
|
message: DataMessage,
|
|
1146
1182
|
seenBefore: number
|
|
1147
1183
|
) {
|
|
1184
|
+
const signers = message.header.signatures!.publicKeys.map((x) =>
|
|
1185
|
+
x.hashcode()
|
|
1186
|
+
);
|
|
1187
|
+
const from = signers[0];
|
|
1188
|
+
|
|
1189
|
+
this.routes.remoteInfo.get(from)?.session;
|
|
1148
1190
|
if (
|
|
1149
1191
|
(message.header.mode instanceof SeekDelivery ||
|
|
1150
1192
|
message.header.mode instanceof AcknowledgeDelivery) &&
|
|
@@ -1155,12 +1197,10 @@ export abstract class DirectStream<
|
|
|
1155
1197
|
await new ACK({
|
|
1156
1198
|
messageIdToAcknowledge: message.id,
|
|
1157
1199
|
seenCounter: seenBefore,
|
|
1158
|
-
|
|
1159
1200
|
// TODO only give origin info to peers we want to connect to us
|
|
1160
1201
|
header: new MessageHeader({
|
|
1161
|
-
mode: new TracedDelivery(
|
|
1162
|
-
|
|
1163
|
-
),
|
|
1202
|
+
mode: new TracedDelivery(signers),
|
|
1203
|
+
session: this.session,
|
|
1164
1204
|
|
|
1165
1205
|
// include our origin if message is SeekDelivery and we have not recently pruned a connection to this peer
|
|
1166
1206
|
origin:
|
|
@@ -1216,7 +1256,7 @@ export abstract class DirectStream<
|
|
|
1216
1256
|
return false;
|
|
1217
1257
|
}
|
|
1218
1258
|
|
|
1219
|
-
if (!(await
|
|
1259
|
+
if (!(await this.verifyAndProcess(message))) {
|
|
1220
1260
|
logger.warn(`Recieved ACK message that did not verify`);
|
|
1221
1261
|
return false;
|
|
1222
1262
|
}
|
|
@@ -1238,7 +1278,9 @@ export abstract class DirectStream<
|
|
|
1238
1278
|
await this.publishMessage(this.publicKey, message, [nextStream], true);
|
|
1239
1279
|
} else {
|
|
1240
1280
|
if (myIndex !== 0) {
|
|
1241
|
-
throw
|
|
1281
|
+
// TODO should we throw something, or log?
|
|
1282
|
+
// we could arrive here if the ACK target does not exist any more...
|
|
1283
|
+
return;
|
|
1242
1284
|
}
|
|
1243
1285
|
// if origin exist (we can connect to remote peer) && we have autodialer turned on
|
|
1244
1286
|
if (message.header.origin && this.connectionManagerOptions.dialer) {
|
|
@@ -1269,7 +1311,7 @@ export abstract class DirectStream<
|
|
|
1269
1311
|
return;
|
|
1270
1312
|
}
|
|
1271
1313
|
|
|
1272
|
-
if (!(await
|
|
1314
|
+
if (!(await this.verifyAndProcess(message))) {
|
|
1273
1315
|
logger.warn(`Recieved ACK message that did not verify`);
|
|
1274
1316
|
return false;
|
|
1275
1317
|
}
|
|
@@ -1278,6 +1320,10 @@ export abstract class DirectStream<
|
|
|
1278
1320
|
this.routes.hasTarget(x)
|
|
1279
1321
|
);
|
|
1280
1322
|
|
|
1323
|
+
for (const remote of filteredLeaving) {
|
|
1324
|
+
this.invalidateSession(remote);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1281
1327
|
if (filteredLeaving.length > 0) {
|
|
1282
1328
|
this.publish(new Uint8Array(0), {
|
|
1283
1329
|
mode: new SeekDelivery({
|
|
@@ -1338,10 +1384,12 @@ export abstract class DirectStream<
|
|
|
1338
1384
|
const neighbourRoutes = this.routes.routes
|
|
1339
1385
|
.get(this.publicKeyHash)
|
|
1340
1386
|
?.get(hash);
|
|
1387
|
+
|
|
1341
1388
|
if (
|
|
1342
1389
|
!neighbourRoutes ||
|
|
1343
|
-
now - neighbourRoutes.
|
|
1344
|
-
neighbourRoutes.list.length * this.routeSeekInterval
|
|
1390
|
+
now - neighbourRoutes.session >
|
|
1391
|
+
neighbourRoutes.list.length * this.routeSeekInterval ||
|
|
1392
|
+
!this.routes.isUpToDate(hash, neighbourRoutes)
|
|
1345
1393
|
) {
|
|
1346
1394
|
mode = new SeekDelivery({
|
|
1347
1395
|
to: mode.to,
|
|
@@ -1354,18 +1402,11 @@ export abstract class DirectStream<
|
|
|
1354
1402
|
|
|
1355
1403
|
const message = new DataMessage({
|
|
1356
1404
|
data: data instanceof Uint8ArrayList ? data.subarray() : data,
|
|
1357
|
-
header: new MessageHeader({ mode })
|
|
1405
|
+
header: new MessageHeader({ mode, session: this.session })
|
|
1358
1406
|
});
|
|
1359
1407
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
mode instanceof SeekDelivery ||
|
|
1363
|
-
mode instanceof AcknowledgeDelivery ||
|
|
1364
|
-
mode instanceof AnyWhere
|
|
1365
|
-
) {
|
|
1366
|
-
await message.sign(this.sign);
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1408
|
+
// TODO allow messages to also be sent unsigned (signaturePolicy property)
|
|
1409
|
+
await message.sign(this.sign);
|
|
1369
1410
|
return message;
|
|
1370
1411
|
}
|
|
1371
1412
|
/**
|
|
@@ -1530,7 +1571,8 @@ export abstract class DirectStream<
|
|
|
1530
1571
|
messageThrough.publicKey.hashcode(),
|
|
1531
1572
|
messageTarget,
|
|
1532
1573
|
seenCounter,
|
|
1533
|
-
session
|
|
1574
|
+
session,
|
|
1575
|
+
Number(ack.header.session)
|
|
1534
1576
|
); // we assume the seenCounter = distance. The more the message has been seen by the target the longer the path is to the target
|
|
1535
1577
|
}
|
|
1536
1578
|
|
|
@@ -1790,8 +1832,6 @@ export abstract class DirectStream<
|
|
|
1790
1832
|
return this._ackCallbacks.size > 0;
|
|
1791
1833
|
}
|
|
1792
1834
|
|
|
1793
|
-
lastQueuedBytes = 0;
|
|
1794
|
-
|
|
1795
1835
|
// make this into a job? run every few ms
|
|
1796
1836
|
maybePruneConnections(): Promise<void> {
|
|
1797
1837
|
if (this.connectionManagerOptions.pruner) {
|
|
@@ -1823,8 +1863,6 @@ export abstract class DirectStream<
|
|
|
1823
1863
|
if (this.peers.size <= this.connectionManagerOptions.minConnections) {
|
|
1824
1864
|
return;
|
|
1825
1865
|
}
|
|
1826
|
-
console.log("PRUNE CONNECTIONS");
|
|
1827
|
-
|
|
1828
1866
|
const sorted = [...this.peers.values()]
|
|
1829
1867
|
.sort((x, y) => x.usedBandwidth - y.usedBandwidth)
|
|
1830
1868
|
.map((x) => x.publicKey.hashcode());
|
package/src/metrics.ts
CHANGED
package/src/routes.ts
CHANGED
|
@@ -1,35 +1,38 @@
|
|
|
1
1
|
import { AbortError, delay } from "@peerbit/time";
|
|
2
2
|
|
|
3
3
|
export const MAX_ROUTE_DISTANCE = Number.MAX_SAFE_INTEGER - 1;
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
type RelayInfo = {
|
|
5
6
|
session: number;
|
|
6
7
|
hash: string;
|
|
7
8
|
expireAt?: number;
|
|
8
9
|
distance: number;
|
|
9
10
|
};
|
|
11
|
+
type RouteInfo = {
|
|
12
|
+
remoteSession: number;
|
|
13
|
+
session: number;
|
|
14
|
+
list: RelayInfo[];
|
|
15
|
+
};
|
|
16
|
+
|
|
10
17
|
export class Routes {
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
routes: Map<
|
|
14
|
-
string,
|
|
15
|
-
Map<
|
|
16
|
-
string,
|
|
17
|
-
{
|
|
18
|
-
latestSession: number;
|
|
19
|
-
list: RouteInfo[];
|
|
20
|
-
}
|
|
21
|
-
>
|
|
22
|
-
> = new Map();
|
|
18
|
+
// FROM -> TO -> { ROUTE INFO, A list of neighbours that we can send data through to reach to}
|
|
19
|
+
routes: Map<string, Map<string, RouteInfo>> = new Map();
|
|
23
20
|
|
|
21
|
+
remoteInfo: Map<string, { session?: number }> = new Map();
|
|
22
|
+
|
|
23
|
+
// Maximum amount of time to retain routes that are not valid anymore
|
|
24
|
+
// once we receive new route info to reach a specific target
|
|
24
25
|
routeMaxRetentionPeriod: number;
|
|
25
|
-
|
|
26
|
+
|
|
27
|
+
signal?: AbortSignal;
|
|
26
28
|
|
|
27
29
|
constructor(
|
|
28
30
|
readonly me: string,
|
|
29
|
-
options
|
|
31
|
+
options?: { routeMaxRetentionPeriod?: number; signal?: AbortSignal }
|
|
30
32
|
) {
|
|
31
|
-
this.routeMaxRetentionPeriod =
|
|
32
|
-
|
|
33
|
+
this.routeMaxRetentionPeriod =
|
|
34
|
+
options?.routeMaxRetentionPeriod ?? 10 * 1000;
|
|
35
|
+
this.signal = options?.signal;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
clear() {
|
|
@@ -42,7 +45,7 @@ export class Routes {
|
|
|
42
45
|
const map = fromMap.get(to);
|
|
43
46
|
if (map) {
|
|
44
47
|
const now = +new Date();
|
|
45
|
-
const keepRoutes:
|
|
48
|
+
const keepRoutes: RelayInfo[] = [];
|
|
46
49
|
for (const route of map.list) {
|
|
47
50
|
// delete all routes after a while
|
|
48
51
|
if (route.expireAt != null && route.expireAt < now) {
|
|
@@ -68,8 +71,9 @@ export class Routes {
|
|
|
68
71
|
neighbour: string,
|
|
69
72
|
target: string,
|
|
70
73
|
distance: number,
|
|
71
|
-
session: number
|
|
72
|
-
|
|
74
|
+
session: number,
|
|
75
|
+
remoteSession: number
|
|
76
|
+
): "new" | "updated" | "restart" {
|
|
73
77
|
let fromMap = this.routes.get(from);
|
|
74
78
|
if (!fromMap) {
|
|
75
79
|
fromMap = new Map();
|
|
@@ -77,22 +81,42 @@ export class Routes {
|
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
let prev = fromMap.get(target);
|
|
80
|
-
|
|
84
|
+
const routeDidExist = prev;
|
|
85
|
+
const isNewSession = !prev || session > prev.session;
|
|
81
86
|
if (!prev) {
|
|
82
|
-
prev = {
|
|
87
|
+
prev = { session, remoteSession, list: [] as RelayInfo[] };
|
|
83
88
|
fromMap.set(target, prev);
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
if (neighbour === target) {
|
|
92
|
+
if (from === this.me) {
|
|
93
|
+
// force distance to neighbour as targets to always favor directly sending to them
|
|
94
|
+
// i.e. if target is our neighbour, always assume the shortest path to them is the direct path
|
|
95
|
+
distance = -1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// we do this since don't care about remote session if the
|
|
99
|
+
//peer is our neighbour since we will always be up to date by connect/disconnect events
|
|
100
|
+
remoteSession = -1;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let isNewRemoteSession = false;
|
|
104
|
+
if (routeDidExist) {
|
|
105
|
+
if (prev.remoteSession != -1) {
|
|
106
|
+
// if the remote session is later, we consider that the remote has 'restarted'
|
|
107
|
+
isNewRemoteSession = remoteSession > (prev.remoteSession || -1);
|
|
108
|
+
prev.remoteSession =
|
|
109
|
+
remoteSession === -1
|
|
110
|
+
? remoteSession
|
|
111
|
+
: Math.max(remoteSession, prev.remoteSession || -1);
|
|
112
|
+
} else {
|
|
113
|
+
isNewRemoteSession = false;
|
|
114
|
+
}
|
|
90
115
|
}
|
|
91
116
|
|
|
92
|
-
|
|
93
|
-
const isNewSession = session > prev.latestSession;
|
|
94
|
-
prev.latestSession = Math.max(session, prev.latestSession);
|
|
117
|
+
prev.session = Math.max(session, prev.session);
|
|
95
118
|
|
|
119
|
+
// Update routes and cleanup all old routes that are older than latest session - some threshold
|
|
96
120
|
if (isNewSession) {
|
|
97
121
|
// Mark previous routes as old
|
|
98
122
|
|
|
@@ -121,6 +145,7 @@ export class Routes {
|
|
|
121
145
|
}
|
|
122
146
|
|
|
123
147
|
// Modify list for new/update route
|
|
148
|
+
let exist = false;
|
|
124
149
|
for (const route of prev.list) {
|
|
125
150
|
if (route.hash === neighbour) {
|
|
126
151
|
// if route is faster or just as fast, update existing route
|
|
@@ -129,13 +154,14 @@ export class Routes {
|
|
|
129
154
|
route.session = session;
|
|
130
155
|
route.expireAt = undefined; // remove expiry since we updated
|
|
131
156
|
prev.list.sort((a, b) => a.distance - b.distance);
|
|
132
|
-
return;
|
|
157
|
+
return isNewRemoteSession ? "restart" : "updated";
|
|
133
158
|
} else if (route.distance === distance) {
|
|
134
159
|
route.session = session;
|
|
135
160
|
route.expireAt = undefined; // remove expiry since we updated
|
|
136
|
-
return;
|
|
161
|
+
return isNewRemoteSession ? "restart" : "updated";
|
|
137
162
|
}
|
|
138
163
|
|
|
164
|
+
exist = true;
|
|
139
165
|
// else break and push the route as a new route (that ought to be longer)
|
|
140
166
|
break;
|
|
141
167
|
}
|
|
@@ -143,33 +169,36 @@ export class Routes {
|
|
|
143
169
|
|
|
144
170
|
prev.list.push({ distance, session, hash: neighbour });
|
|
145
171
|
prev.list.sort((a, b) => a.distance - b.distance);
|
|
172
|
+
return exist ? (isNewRemoteSession ? "restart" : "updated") : "new";
|
|
146
173
|
}
|
|
147
174
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
this.routes.delete(fromMapKey);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return [target];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
removeNeighbour(target: string) {
|
|
175
|
+
/**
|
|
176
|
+
*
|
|
177
|
+
* @param target
|
|
178
|
+
* @returns unreachable nodes (from me) after removal
|
|
179
|
+
*/
|
|
180
|
+
remove(target: string) {
|
|
161
181
|
this.routes.delete(target);
|
|
162
|
-
const maybeUnreachable: Set<string> = new Set(
|
|
182
|
+
const maybeUnreachable: Set<string> = new Set();
|
|
183
|
+
let targetRemoved = false;
|
|
163
184
|
for (const [fromMapKey, fromMap] of this.routes) {
|
|
164
185
|
// delete target
|
|
165
|
-
fromMap.delete(target);
|
|
186
|
+
const deletedAsTarget = fromMap.delete(target);
|
|
187
|
+
targetRemoved =
|
|
188
|
+
targetRemoved || (deletedAsTarget && fromMapKey === this.me);
|
|
166
189
|
|
|
167
190
|
// delete this as neighbour
|
|
168
191
|
for (const [remote, neighbours] of fromMap) {
|
|
169
|
-
|
|
192
|
+
const filtered = neighbours.list.filter((x) => x.hash !== target);
|
|
193
|
+
neighbours.list = filtered;
|
|
170
194
|
if (neighbours.list.length === 0) {
|
|
171
195
|
fromMap.delete(remote);
|
|
172
|
-
|
|
196
|
+
|
|
197
|
+
if (fromMapKey === this.me) {
|
|
198
|
+
// TODO we only return maybeUnreachable if the route starts from me.
|
|
199
|
+
// expected?
|
|
200
|
+
maybeUnreachable.add(remote);
|
|
201
|
+
}
|
|
173
202
|
}
|
|
174
203
|
}
|
|
175
204
|
|
|
@@ -177,6 +206,11 @@ export class Routes {
|
|
|
177
206
|
this.routes.delete(fromMapKey);
|
|
178
207
|
}
|
|
179
208
|
}
|
|
209
|
+
this.remoteInfo.delete(target);
|
|
210
|
+
|
|
211
|
+
if (targetRemoved) {
|
|
212
|
+
maybeUnreachable.add(target);
|
|
213
|
+
}
|
|
180
214
|
return [...maybeUnreachable].filter((x) => !this.isReachable(this.me, x));
|
|
181
215
|
}
|
|
182
216
|
|
|
@@ -185,18 +219,29 @@ export class Routes {
|
|
|
185
219
|
}
|
|
186
220
|
|
|
187
221
|
isReachable(from: string, target: string, maxDistance = MAX_ROUTE_DISTANCE) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
222
|
+
const remoteInfo = this.remoteInfo.get(target);
|
|
223
|
+
const routeInfo = this.routes.get(from)?.get(target);
|
|
224
|
+
if (!routeInfo) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
if (!remoteInfo) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
if (
|
|
231
|
+
routeInfo.remoteSession == undefined ||
|
|
232
|
+
remoteInfo.session === undefined
|
|
233
|
+
) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
193
236
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (!path) {
|
|
237
|
+
if (routeInfo.remoteSession < remoteInfo.session) {
|
|
238
|
+
// route info is older than remote info
|
|
197
239
|
return false;
|
|
198
240
|
}
|
|
199
|
-
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
(routeInfo?.list[0]?.distance ?? Number.MAX_SAFE_INTEGER) <= maxDistance
|
|
244
|
+
);
|
|
200
245
|
}
|
|
201
246
|
|
|
202
247
|
hasTarget(target: string) {
|
|
@@ -208,11 +253,68 @@ export class Routes {
|
|
|
208
253
|
return false;
|
|
209
254
|
}
|
|
210
255
|
|
|
211
|
-
|
|
256
|
+
updateSession(remote: string, session?: number) {
|
|
257
|
+
if (session == null) {
|
|
258
|
+
this.remoteInfo.delete(remote);
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const remoteInfo = this.remoteInfo.get(remote);
|
|
263
|
+
if (remoteInfo) {
|
|
264
|
+
// remote has restartet, mark all routes originating from me to the remote as 'old'
|
|
265
|
+
if (remoteInfo.session === -1) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
if (session === -1) {
|
|
269
|
+
remoteInfo.session = -1;
|
|
270
|
+
return false;
|
|
271
|
+
} else {
|
|
272
|
+
if (session > (remoteInfo.session || -1)) {
|
|
273
|
+
remoteInfo.session = session;
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
} else if (session !== undefined) {
|
|
279
|
+
this.remoteInfo.set(remote, { session });
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
getSession(remote: string): number | undefined {
|
|
286
|
+
return this.remoteInfo.get(remote)?.session;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
isUpToDate(target: string, route: RouteInfo) {
|
|
290
|
+
const peerInfo = this.remoteInfo.get(target);
|
|
291
|
+
return peerInfo?.session != null && route.remoteSession >= peerInfo.session;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
getDependent(peer: string) {
|
|
212
295
|
const dependent: string[] = [];
|
|
213
|
-
|
|
214
|
-
|
|
296
|
+
|
|
297
|
+
outer: for (const [fromMapKey, fromMap] of this.routes) {
|
|
298
|
+
if (fromMapKey !== this.me) {
|
|
299
|
+
continue; // skip this because these routes are starting from me. We are looking for routes that affect others
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// If the route is to the target
|
|
303
|
+
// tell 'from' that it is no longer reachable
|
|
304
|
+
if (fromMap.has(peer)) {
|
|
215
305
|
dependent.push(fromMapKey);
|
|
306
|
+
continue outer;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// If the relay is dependent of peer
|
|
310
|
+
// tell 'from' that it is no longer reachable
|
|
311
|
+
for (const [_to, through] of fromMap) {
|
|
312
|
+
for (const neighbour of through.list) {
|
|
313
|
+
if (neighbour.hash === peer) {
|
|
314
|
+
dependent.push(fromMapKey);
|
|
315
|
+
continue outer;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
216
318
|
}
|
|
217
319
|
}
|
|
218
320
|
return dependent;
|
|
@@ -284,7 +386,7 @@ export class Routes {
|
|
|
284
386
|
fanout.set(to, { to, timestamp: session });
|
|
285
387
|
|
|
286
388
|
if (
|
|
287
|
-
(distance == 0 && session === neighbour.
|
|
389
|
+
(distance == 0 && session === neighbour.session) ||
|
|
288
390
|
distance == -1
|
|
289
391
|
) {
|
|
290
392
|
foundClosest = true;
|