@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/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
- > = new Map();
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.signaturePolicy = signaturePolicy;
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, true);
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, neigbour: boolean) {
826
- const unreachable = neigbour
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(from, neighbour, targetHash, distance, session);
845
+ const update = this.routes.add(
846
+ from,
847
+ neighbour,
848
+ targetHash,
849
+ distance,
850
+ session,
851
+ remoteSession
852
+ );
849
853
 
850
- const newPeer =
851
- wasReachable === false && this.routes.isReachable(from, targetHash);
852
- if (newPeer) {
853
- this.peerKeyHashToPublicKey.set(target.hashcode(), target);
854
- this.onPeerReachable(target); // TODO types
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.maybeVerifyMessage(message)) === false) {
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 maybeVerifyMessage(message: DataMessage) {
1140
- return message.verify(this.signaturePolicy === "StictSign");
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
- message.header.signatures!.publicKeys.map((x) => x.hashcode())
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 message.verify(true))) {
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 new Error("Unexpected");
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 message.verify(true))) {
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.latestSession >
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
- if (
1361
- this.signaturePolicy === "StictSign" ||
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
@@ -1,5 +1,3 @@
1
- import { PublicSignKey } from "@peerbit/crypto";
2
-
3
1
  export class MovingAverageTracker {
4
2
  private lastTS = 0;
5
3
 
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
- type RouteInfo = {
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
- // END receiver -> Neighbour
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
- signal: AbortSignal;
26
+
27
+ signal?: AbortSignal;
26
28
 
27
29
  constructor(
28
30
  readonly me: string,
29
- options: { routeMaxRetentionPeriod: number; signal: AbortSignal }
31
+ options?: { routeMaxRetentionPeriod?: number; signal?: AbortSignal }
30
32
  ) {
31
- this.routeMaxRetentionPeriod = options.routeMaxRetentionPeriod;
32
- this.signal = options.signal;
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: RouteInfo[] = [];
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 = { latestSession: 0, list: [] as RouteInfo[] };
87
+ prev = { session, remoteSession, list: [] as RelayInfo[] };
83
88
  fromMap.set(target, prev);
84
89
  }
85
90
 
86
- if (from === this.me && neighbour === target) {
87
- // force distance to neighbour as targets to always favor directly sending to them
88
- // i.e. if target is our neighbour, always assume the shortest path to them is the direct path
89
- distance = -1;
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
- // Update routes and cleanup all old routes that are older than latest session - some threshold
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
- removeTarget(target: string) {
149
- this.routes.delete(target);
150
- for (const [fromMapKey, fromMap] of this.routes) {
151
- // delete target
152
- fromMap.delete(target);
153
- if (fromMap.size === 0) {
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([target]);
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
- neighbours.list = neighbours.list.filter((x) => x.hash !== target);
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
- maybeUnreachable.add(remote);
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
- return (
189
- (this.routes.get(from)?.get(target)?.list[0]?.distance ??
190
- Number.MAX_SAFE_INTEGER) <= maxDistance
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
- hasShortestPath(target: string) {
195
- const path = this.routes.get(this.me)?.get(target);
196
- if (!path) {
237
+ if (routeInfo.remoteSession < remoteInfo.session) {
238
+ // route info is older than remote info
197
239
  return false;
198
240
  }
199
- return path.list[0].distance <= 0;
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
- getDependent(target: string) {
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
- for (const [fromMapKey, fromMap] of this.routes) {
214
- if (fromMapKey !== this.me && fromMap.has(target)) {
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.latestSession) ||
389
+ (distance == 0 && session === neighbour.session) ||
288
390
  distance == -1
289
391
  ) {
290
392
  foundClosest = true;