querysub 0.507.0 → 0.509.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.
Files changed (50) hide show
  1. package/bin/function-public.js +2 -0
  2. package/bin/server.js +0 -2
  3. package/package.json +3 -4
  4. package/spec.txt +5 -0
  5. package/src/-f-node-discovery/NodeDiscovery.ts +1 -1
  6. package/src/-g-core-values/NodeCapabilities.ts +4 -0
  7. package/src/-g-core-values/scheduledShutdown.ts +48 -0
  8. package/src/0-path-value-core/AuthorityLookup.ts +50 -9
  9. package/src/0-path-value-core/PathRouter.ts +10 -31
  10. package/src/0-path-value-core/PathRouterRouteOverride.ts +14 -98
  11. package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +1 -4
  12. package/src/0-path-value-core/PathValueCommitter.ts +1 -6
  13. package/src/0-path-value-core/PathValueController.ts +5 -7
  14. package/src/0-path-value-core/PathWatcher.ts +8 -11
  15. package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +8 -22
  16. package/src/0-path-value-core/pathValueCore.ts +7 -14
  17. package/src/1-path-client/RemoteWatcher.ts +47 -34
  18. package/src/2-proxy/archiveMoveHarness.ts +2 -1
  19. package/src/3-path-functions/PathFunctionHelpers.ts +11 -10
  20. package/src/3-path-functions/PathFunctionRunner.ts +165 -24
  21. package/src/3-path-functions/functionReplay.ts +2 -0
  22. package/src/3-path-functions/syncSchema.ts +0 -2
  23. package/src/4-deploy/edgeBootstrap.ts +10 -0
  24. package/src/4-deploy/edgeClientWatcher.tsx +4 -6
  25. package/src/4-deploy/edgeNodes.ts +52 -12
  26. package/src/4-querysub/FunctionRunnerTracking.ts +290 -0
  27. package/src/4-querysub/Querysub.ts +11 -16
  28. package/src/4-querysub/QuerysubController.ts +13 -11
  29. package/src/4-querysub/permissions.ts +3 -0
  30. package/src/4-querysub/querysubPrediction.ts +2 -2
  31. package/src/config.ts +1 -3
  32. package/src/deployManager/components/ServiceDetailPage.tsx +127 -73
  33. package/src/deployManager/components/ServicesListPage.tsx +3 -3
  34. package/src/deployManager/machineApplyMainCode.ts +139 -153
  35. package/src/deployManager/machineController.ts +12 -61
  36. package/src/deployManager/machineSchema.ts +84 -3
  37. package/src/deployManager/setupMachineMain.ts +2 -1
  38. package/src/diagnostics/grossStats/GrossStatsController.ts +3 -2
  39. package/src/diagnostics/logs/errorNotifications2/errorNotifications.ts +2 -2
  40. package/src/diagnostics/logs/errorTickets/autoFixer.ts +3 -2
  41. package/src/diagnostics/logs/errorTickets/tickets.ts +1 -5
  42. package/src/diagnostics/logs/logGitHashes.ts +1 -1
  43. package/src/diagnostics/managementPages.tsx +2 -0
  44. package/src/diagnostics/misc-pages/AuthoritySpecPage.tsx +36 -28
  45. package/src/diagnostics/misc-pages/FunctionCaptureAnalyzePage.tsx +1 -1
  46. package/src/library-components/NetworkSelector.tsx +77 -0
  47. package/src/misc.ts +19 -0
  48. package/src/user-implementation/userData.ts +1 -1
  49. package/src/zipThreaded.ts +3 -2
  50. package/bin/server-dev.js +0 -11
@@ -2,6 +2,8 @@
2
2
 
3
3
  process.argv.push("--nobreak");
4
4
  process.argv.push("--public");
5
+ process.argv.push("--network");
6
+ process.argv.push("public");
5
7
 
6
8
  require("typenode");
7
9
  require("../src/3-path-functions/PathFunctionRunnerMain");
package/bin/server.js CHANGED
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.argv.push("--local");
4
- process.argv.push("--networkfile");
5
- process.argv.push("~/devnetwork.txt");
6
4
 
7
5
  require("typenode");
8
6
  require("../src/server.ts");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.507.0",
3
+ "version": "0.509.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -31,7 +31,6 @@
31
31
  "deploy": "./bin/deploy.js",
32
32
  "deploy-prefixes": "./bin/deploy-prefixes.js",
33
33
  "server": "./bin/server.js",
34
- "server-dev": "./bin/server-dev.js",
35
34
  "server-public": "./bin/server-public.js",
36
35
  "function": "./bin/function.js",
37
36
  "function-public": "./bin/function-public.js",
@@ -71,10 +70,10 @@
71
70
  "pako": "^2.1.0",
72
71
  "peggy": "^5.0.6",
73
72
  "sliftutils": "^1.7.5",
74
- "socket-function": "^1.2.4",
73
+ "socket-function": "^1.2.5",
75
74
  "terser": "^5.31.0",
76
75
  "typenode": "^6.6.1",
77
- "typesafecss": "*",
76
+ "typesafecss": "^0.32.0",
78
77
  "yaml": "^2.5.0",
79
78
  "yargs": "^15.3.1"
80
79
  },
package/spec.txt CHANGED
@@ -1,4 +1,9 @@
1
1
 
2
+ todonext
3
+ Review the network trakcing code again.
4
+ - Maybe manually refactor it
5
+ Ensure there is no dumb sync code
6
+
2
7
 
3
8
 
4
9
  Trigger values
@@ -24,7 +24,7 @@ import { logDisk } from "../diagnostics/logs/diskLogger";
24
24
  import { MaybePromise } from "socket-function/src/types";
25
25
  import { getPathStr2 } from "../path";
26
26
 
27
- let HEARTBEAT_INTERVAL = timeInMinute * 15;
27
+ let HEARTBEAT_INTERVAL = timeInMinute * 5;
28
28
  // Interval which we check other heartbeats
29
29
  let CHECK_INTERVAL = HEARTBEAT_INTERVAL;
30
30
  // If the heartbeat is older than thing, it fails the dead check
@@ -24,6 +24,7 @@ import { getPathStr2 } from "../path";
24
24
  import { PromiseObj } from "../promise";
25
25
  import { getTrueTimeOffset } from "socket-function/time/trueTimeShim";
26
26
  import { getGitRefLive } from "../4-deploy/git";
27
+ import { getScheduledShutdownTime } from "./scheduledShutdown";
27
28
  setImmediate(() => {
28
29
  import("../diagnostics/MachineThreadInfo");
29
30
  });
@@ -139,6 +140,8 @@ export type NodeMetadata = {
139
140
  exposedControllers: string[];
140
141
  trueTimeOffset: number;
141
142
  gitRef: string;
143
+ // Set when the machine apply process has told us we will be shut down (due to a scheduled switchover)
144
+ scheduledShutdownTime?: number;
142
145
  };
143
146
 
144
147
  class NodeCapabilitiesControllerBase {
@@ -153,6 +156,7 @@ class NodeCapabilitiesControllerBase {
153
156
  exposedControllers: Array.from(SocketFunction.exposedClasses),
154
157
  trueTimeOffset: getTrueTimeOffset(),
155
158
  gitRef: await getGitRefLive(),
159
+ scheduledShutdownTime: getScheduledShutdownTime(),
156
160
  };
157
161
  }
158
162
 
@@ -0,0 +1,48 @@
1
+ /*
2
+ Tracks when OUR node is scheduled to shut down (told to us by the machine apply process when a service switchover is scheduled). Other systems watch this to gracefully wind down ahead of the shutdown (deregistering as an edge node, rehoming watched paths, etc). The time is also exposed in our NodeCapabilities metadata, so other nodes can stop using us before we shutdown.
3
+ */
4
+
5
+ const IMMINENT_SHUTDOWN_WARNING_TIME = 60_000;
6
+
7
+ let scheduledShutdownTime: number | undefined;
8
+ let watchers = new Set<(time: number) => void>();
9
+ let imminentLogTimer: ReturnType<typeof setTimeout> | undefined;
10
+
11
+ export function getScheduledShutdownTime(): number | undefined {
12
+ return scheduledShutdownTime;
13
+ }
14
+
15
+ export function setScheduledShutdownTime(time: number) {
16
+ console.log(`Our node is scheduled to shut down at ${new Date(time).toLocaleString()} (in ${Math.round((time - Date.now()) / 1000)}s) due to a service switchover`);
17
+ scheduledShutdownTime = time;
18
+
19
+ if (imminentLogTimer) {
20
+ clearTimeout(imminentLogTimer);
21
+ imminentLogTimer = undefined;
22
+ }
23
+ // setTimeout overflows (fires immediately) past ~24.8 days, and switchovers can be scheduled weeks out, so re-arm until we're within range.
24
+ const MAX_TIMEOUT_DELAY = 2 ** 31 - 1;
25
+ const armImminentLog = () => {
26
+ let delay = time - Date.now() - IMMINENT_SHUTDOWN_WARNING_TIME;
27
+ if (delay > MAX_TIMEOUT_DELAY) {
28
+ imminentLogTimer = setTimeout(armImminentLog, MAX_TIMEOUT_DELAY);
29
+ return;
30
+ }
31
+ imminentLogTimer = setTimeout(() => {
32
+ console.log(`We are about to be shut down due to a service switchover, at ${new Date(time).toLocaleString()}`);
33
+ }, Math.max(0, delay));
34
+ };
35
+ armImminentLog();
36
+
37
+ for (let watcher of watchers) {
38
+ watcher(time);
39
+ }
40
+ }
41
+
42
+ /** Calls the callback whenever our scheduled shutdown time is set or changed (immediately if it is already set). */
43
+ export function watchScheduledShutdown(callback: (time: number) => void) {
44
+ watchers.add(callback);
45
+ if (scheduledShutdownTime !== undefined) {
46
+ callback(scheduledShutdownTime);
47
+ }
48
+ }
@@ -11,21 +11,35 @@ import { IdentityController_getCurrentReconnectNodeIdAssert } from "../-c-identi
11
11
  import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
12
12
  import { isClient } from "../config2";
13
13
  import { getPathStr1 } from "../path";
14
- import { timeoutToError } from "../errors";
14
+ import { logErrors, timeoutToError } from "../errors";
15
15
  import { AuthoritySpec, parsePrefixMatcher } from "./PathRouter";
16
+
16
17
  import { formatTime } from "socket-function/src/formatting/format";
17
18
  import { getAllAuthoritySpec, getEmptyAuthoritySpec } from "./PathRouterServerAuthoritySpec";
19
+ import { getScheduledShutdownTime, watchScheduledShutdown } from "../-g-core-values/scheduledShutdown";
18
20
 
19
21
  setImmediate(() => import("../3-path-functions/syncSchema"));
20
22
 
21
23
  let NETWORK_POLL_INTERVAL = timeInMinute * 1;
22
24
  let CALL_TIMEOUT = timeInSecond * 5;
23
25
 
26
+ // Every node's per-node poll is created together at discovery, so without jitter all of their
27
+ // networkSyncPaths calls fire in lockstep each NETWORK_POLL_INTERVAL — and when many nodes are
28
+ // dead their CALL_TIMEOUT waits pile up into one spike. Each node instead drifts an extra wait
29
+ // by its own random velocity (wrapping at this window), which spreads the calls out and keeps
30
+ // them from re-aligning over time. Kept small so no single sync is delayed by much.
31
+ const SYNC_JITTER_WINDOW = timeInSecond * 10;
32
+
33
+ // Nodes this close to their scheduled shutdown are avoided as sources (only used if nothing else can satisfy the request).
34
+ const SHUTDOWN_AVOID_WINDOW = timeInMinute * 10;
35
+
24
36
  export type AuthorityEntry = {
25
37
  nodeId: string;
26
38
  authoritySpec: AuthoritySpec;
27
39
  entryReceivedTime: number;
28
40
  isReady: boolean;
41
+ // This can be far in the future (a switchover scheduled weeks out), so routing doesn't avoid the node immediately — only once within SHUTDOWN_AVOID_WINDOW (see nodeIsShuttingDownSoon).
42
+ scheduledShutdownTime?: number;
29
43
  };
30
44
  type AuthorityTopology = {
31
45
  // nodeId =>
@@ -127,15 +141,21 @@ class AuthorityLookup {
127
141
  });
128
142
  await Promise.all(promises);
129
143
  this.didInitialSync = true;
144
+
145
+ // Push our scheduled shutdown time to the other nodes right away, so they can start avoiding us (instead of waiting up to a full poll interval).
146
+ watchScheduledShutdown(() => {
147
+ logErrors(this.syncAllNow());
148
+ });
130
149
  });
131
150
 
132
151
 
133
- private updatePaths(nodeId: string, spec: AuthoritySpec, isReady: boolean) {
152
+ private updatePaths(nodeId: string, spec: AuthoritySpec, isReady: boolean, scheduledShutdownTime?: number) {
134
153
  let entry: AuthorityEntry = {
135
154
  nodeId,
136
155
  authoritySpec: spec,
137
156
  entryReceivedTime: Date.now(),
138
157
  isReady,
158
+ scheduledShutdownTime,
139
159
  };
140
160
  let prevEntry = this.topology.nodes.get(nodeId);
141
161
  // Try to preserve === property where possible, so we can use it as a cache key
@@ -145,22 +165,36 @@ class AuthorityLookup {
145
165
  this.topology.nodes.set(nodeId, entry);
146
166
  }
147
167
 
148
- public async networkSyncPaths(otherSpec: AuthoritySpec, isReady: boolean): Promise<{
168
+ public getScheduledShutdownTime(nodeId: string): number | undefined {
169
+ return this.topology.nodes.get(nodeId)?.scheduledShutdownTime;
170
+ }
171
+ public nodeIsShuttingDownSoon(nodeId: string): boolean {
172
+ let time = this.getScheduledShutdownTime(nodeId);
173
+ return time !== undefined && time - Date.now() < SHUTDOWN_AVOID_WINDOW;
174
+ }
175
+
176
+ public async networkSyncPaths(config: {
149
177
  spec: AuthoritySpec;
150
178
  isReady: boolean;
179
+ scheduledShutdownTime?: number;
180
+ }): Promise<{
181
+ spec: AuthoritySpec;
182
+ isReady: boolean;
183
+ scheduledShutdownTime?: number;
151
184
  }> {
152
185
  let nodeId = IdentityController_getCurrentReconnectNodeIdAssert();
153
- this.updatePaths(nodeId, otherSpec, isReady);
186
+ this.updatePaths(nodeId, config.spec, config.isReady, config.scheduledShutdownTime);
154
187
  console.info(`Received network sync, returning our data`, {
155
188
  nodeId,
156
- otherSpec,
157
- isReady,
189
+ otherSpec: config.spec,
190
+ isReady: config.isReady,
158
191
  ourSpec: this.ourSpec,
159
192
  ourIsReady: this.ourIsReady,
160
193
  });
161
194
  return {
162
195
  spec: this.ourSpec,
163
196
  isReady: this.ourIsReady,
197
+ scheduledShutdownTime: getScheduledShutdownTime(),
164
198
  };
165
199
  }
166
200
 
@@ -169,9 +203,13 @@ class AuthorityLookup {
169
203
 
170
204
  private connectTo = cache((nodeId: string) => {
171
205
  let stopObj = { stop: false };
206
+ let syncJitter = Math.random() * NETWORK_POLL_INTERVAL;
207
+ let syncJitterVelocity = Math.random() * SYNC_JITTER_WINDOW;
172
208
  let promise = runInfinitePollCallAtStart(NETWORK_POLL_INTERVAL, async () => {
173
209
  // NOTE: We keep calling even on disconnect, until it is removed from node discovery, so we handle reconnections. AND if it is removed from node discovery and reconnects, that's fine, it will tell us about the rediscovery as if was a new node, and we will setup it again.
174
210
  await this.syncNodeSerial(nodeId)();
211
+ syncJitter = (syncJitter + syncJitterVelocity) % SYNC_JITTER_WINDOW;
212
+ await delay(syncJitter);
175
213
  }, stopObj);
176
214
 
177
215
  const onNodeRemoved = () => {
@@ -219,14 +257,17 @@ class AuthorityLookup {
219
257
  prefixes: prefixes.map(p => parsePrefixMatcher(p)),
220
258
  routeStart: 0,
221
259
  routeEnd: 1,
222
- networks: "all",
223
260
  }, true);
224
261
  return;
225
262
  }
226
263
 
227
264
  let otherPaths = await timeoutToError(
228
265
  CALL_TIMEOUT,
229
- AuthorityLookupController.nodes[nodeId].networkSyncPaths(this.ourSpec, this.ourIsReady),
266
+ AuthorityLookupController.nodes[nodeId].networkSyncPaths({
267
+ spec: this.ourSpec,
268
+ isReady: this.ourIsReady,
269
+ scheduledShutdownTime: getScheduledShutdownTime(),
270
+ }),
230
271
  () => new Error(`Timeout calling networkSyncPaths for ${nodeId}`)
231
272
  );
232
273
  console.info(`Finished network sync`, {
@@ -236,7 +277,7 @@ class AuthorityLookup {
236
277
  ourSpec: this.ourSpec,
237
278
  ourIsReady: this.ourIsReady,
238
279
  });
239
- this.updatePaths(nodeId, otherPaths.spec, otherPaths.isReady);
280
+ this.updatePaths(nodeId, otherPaths.spec, otherPaths.isReady, otherPaths.scheduledShutdownTime);
240
281
  this.disconnectWatch(nodeId);
241
282
  }
242
283
 
@@ -8,7 +8,6 @@ import { getOwnNodeId, isOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
8
8
  import { unique } from "../misc";
9
9
  import { measureFnc } from "socket-function/src/profiling/measure";
10
10
  import { getRoutingOverride, hasPrefixHash } from "./PathRouterRouteOverride";
11
- import { DEFAULT_NETWORK } from "../config";
12
11
  import { sha256 } from "js-sha256";
13
12
  import { rangesOverlap, removeRange } from "../rangeMath";
14
13
  import { decodeParentFilter } from "./hackedPackedPathParentFiltering";
@@ -38,23 +37,8 @@ export type AuthoritySpec = {
38
37
  prefixes: PrefixMatcher[];
39
38
  // - Make sure to set this if you just want the prefix values, otherwise you will get all that that prefixes don't match (the prefixes exclude prefix match but where route does not match).
40
39
  excludeDefault?: boolean;
41
- // The networks this authority satisfies. Unset is equivalent to ["default"]. Paths only route to non-default networks if their routing override key explicitly asks for that network. "all" is ONLY for client proxy specs (the querysub server the client talks to proxies to the real authorities, which do their own network routing), and is not valid for real authorities.
42
- networks?: string[] | "all";
43
40
  };
44
41
 
45
- function getSpecNetworkList(networks: string[] | undefined): string[] {
46
- return networks && networks.length > 0 && networks || [DEFAULT_NETWORK];
47
- }
48
- export function specSatisfiesNetwork(spec: AuthoritySpec, network: string | undefined): boolean {
49
- if (spec.networks === "all") return true;
50
- return getSpecNetworkList(spec.networks).includes(network || DEFAULT_NETWORK);
51
- }
52
- export function networksOverlap(spec1: AuthoritySpec, spec2: AuthoritySpec): boolean {
53
- if (spec1.networks === "all" || spec2.networks === "all") return true;
54
- let networks2 = getSpecNetworkList(spec2.networks);
55
- return getSpecNetworkList(spec1.networks).some(x => networks2.includes(x));
56
- }
57
-
58
42
  // If you want to match just a simple path, use parsePrefixMatcher. It won't match ===, so get the parent if you want that (and then filter the result).
59
43
  export type PrefixMatcher = {
60
44
  prefix: string;
@@ -158,8 +142,7 @@ export class PathRouter {
158
142
  let { path, spec } = config;
159
143
  path = hack_stripPackedPath(path);
160
144
  let override = getRoutingOverride(path);
161
- if (!specSatisfiesNetwork(spec, override?.network)) return -1;
162
- if (override && override.route !== undefined) {
145
+ if (override) {
163
146
  if (spec.excludeDefault && !hasPrefixHash({ spec, prefixHash: override.prefixHash })) return -1;
164
147
  if (override.route < spec.routeStart || override.route >= spec.routeEnd) return -1;
165
148
  return override.route;
@@ -202,7 +185,7 @@ export class PathRouter {
202
185
  public static getSingleKeyRoute(key: string): number {
203
186
  if (key && this.lastKeyRoute.key === key) return this.lastKeyRoute.route;
204
187
  let override = getRoutingOverride(key);
205
- if (override && override.route !== undefined) {
188
+ if (override) {
206
189
  this.lastKeyRoute.key = key;
207
190
  this.lastKeyRoute.route = override.route;
208
191
  return override.route;
@@ -348,7 +331,6 @@ export class PathRouter {
348
331
  prefixes: group.prefixes,
349
332
  routeStart: 0,
350
333
  routeEnd: 1,
351
- networks: ourSpec.networks,
352
334
  }
353
335
  });
354
336
  let routeIndex = Math.floor(route * splitCount);
@@ -392,7 +374,6 @@ export class PathRouter {
392
374
 
393
375
  @measureFnc
394
376
  public static overlapsAuthority(authority1: AuthoritySpec, authority2: AuthoritySpec): boolean {
395
- if (!networksOverlap(authority1, authority2)) return false;
396
377
  // TODO: This becomes complicated because of exclude default, although I feel like there has to be a way to simplify it? Eh... whatever.
397
378
 
398
379
  let doRangesOverlap = rangesOverlap({ start: authority1.routeStart, end: authority1.routeEnd }, { start: authority2.routeStart, end: authority2.routeEnd });
@@ -457,7 +438,6 @@ export class PathRouter {
457
438
  let { target } = config;
458
439
  let allSources = authorityLookup.getTopologySync();
459
440
  allSources = allSources.filter(x => !isOwnNodeId(x.nodeId));
460
- allSources = allSources.filter(x => networksOverlap(x.authoritySpec, target));
461
441
  // THIS is normal during initial server startup
462
442
  if (!allSources.length) {
463
443
  return [];
@@ -517,7 +497,7 @@ export class PathRouter {
517
497
  let preferredNodeIds = new Set(config?.preferredNodeIds ?? []);
518
498
  for (let group of cohesiveGroups) {
519
499
  shuffle(group, Math.random());
520
- sort(group, x => preferredNodeIds.has(x.nodeId) ? -1 : 1);
500
+ sort(group, x => (authorityLookup.nodeIsShuttingDownSoon(x.nodeId) && 2 || 0) + (preferredNodeIds.has(x.nodeId) && 0 || 1));
521
501
  let targetStart = target.routeStart;
522
502
  let targetEnd = target.routeEnd;
523
503
  // If the group doesn't hash the same way we do, then we need to expand our range to be the full range of the group, so we don't miss anything.
@@ -577,12 +557,10 @@ export class PathRouter {
577
557
  range: { start: number; end: number };
578
558
  }[];
579
559
  } {
580
- let parentFilter = decodeParentFilter(path);
581
- let parentRange = parentFilter || {
560
+ let parentRange = decodeParentFilter(path) || {
582
561
  start: 0,
583
562
  end: 1,
584
563
  };
585
- let parentNetwork = parentFilter?.network;
586
564
  path = hack_stripPackedPath(path);
587
565
  let preferredNodeIds = new Set(config?.preferredNodeIds ?? []);
588
566
 
@@ -591,7 +569,6 @@ export class PathRouter {
591
569
  // - The different route case is how the FuntionRunner works, and without it large databases couldn't run functions. However, most applications won't directly use it.
592
570
  // NOTE: The only own nodes flag is actually so we can access this before the topology finishes synchronizing. Because we want to be able to call get child read nodes from path value core for some really basic stuff. It also is what the caller wants, but that's not a good enough reason to add this check. The reason we have this check is because without it, the topology won't have finished synchronizing and startup won't work.
593
571
  let allSources = config?.onlyOwnNodes ? [{ nodeId: getOwnNodeId(), authoritySpec: authorityLookup.getOurSpec() }] : authorityLookup.getTopologySync();
594
- allSources = allSources.filter(x => specSatisfiesNetwork(x.authoritySpec, parentNetwork));
595
572
  // Prefer our own node
596
573
  sort(allSources, x => isOwnNodeId(x.nodeId) ? -1 : 1);
597
574
 
@@ -603,7 +580,7 @@ export class PathRouter {
603
580
  let hasPrefix = allSources.filter(x => x.authoritySpec.prefixes.some(y => isPrefixParent(y, path))).map(x => x.authoritySpec);
604
581
  if (hasPrefix.length > 0) {
605
582
  shuffle(hasPrefix, Math.random());
606
- sort(hasPrefix, x => preferredNodeIds.has(x.nodeId) ? -1 : 1);
583
+ sort(hasPrefix, x => (authorityLookup.nodeIsShuttingDownSoon(x.nodeId) && 2 || 0) + (preferredNodeIds.has(x.nodeId) && 0 || 1));
607
584
 
608
585
  let missingRanges: { start: number; end: number }[] = [{
609
586
  ...parentRange
@@ -645,7 +622,7 @@ export class PathRouter {
645
622
  });
646
623
  if (nestedMatches.length > 0) {
647
624
  shuffle(nestedMatches, Math.random());
648
- sort(nestedMatches, x => preferredNodeIds.has(x.nodeId) ? -1 : 1);
625
+ sort(nestedMatches, x => (authorityLookup.nodeIsShuttingDownSoon(x.nodeId) && 2 || 0) + (preferredNodeIds.has(x.nodeId) && 0 || 1));
649
626
  sort(allSources, x => isOwnNodeId(x.nodeId) ? -1 : 1);
650
627
  return {
651
628
  // Only need to take the first match. Our path is picked by the prefix, and the prefix only hashes the direct child, and we're more deeply nested than that, which means... the route for all of our children will be identical, so this node matches all of our children.
@@ -664,7 +641,7 @@ export class PathRouter {
664
641
  // Same as prefix matches. Not preferred, and not preferred over being under a prefix, but required for some root data, or data with no prefixes.
665
642
  if (fullPathMatches.length > 0) {
666
643
  shuffle(fullPathMatches, Math.random());
667
- sort(fullPathMatches, x => preferredNodeIds.has(x.nodeId) ? -1 : 1);
644
+ sort(fullPathMatches, x => (authorityLookup.nodeIsShuttingDownSoon(x.nodeId) && 2 || 0) + (preferredNodeIds.has(x.nodeId) && 0 || 1));
668
645
  sort(allSources, x => isOwnNodeId(x.nodeId) ? -1 : 1);
669
646
  let missingRanges: { start: number; end: number }[] = [{
670
647
  ...parentRange
@@ -697,7 +674,7 @@ export class PathRouter {
697
674
  if (!config?.onlyOwnNodes) {
698
675
 
699
676
  // NOTE: We *could* actually synchronize it even if it doesn't have a prefix shard as we can fall back to just the full path sharding. However, it becomes very complicated if we want a specific range, and then it becomes complicated if it then switches to prefix hashing (With the nodes that were using the full path hashing slowly going away). AND... key synchronization IS slow, so it's good to discourage it in general.
700
- console.error(`Want to sync a prefix which is not under an existing prefix, nor equal to a prefix. 1) The servers are down. 1.5) The servers are in an inconsistent state (one shard range added a parent path, the other didn't). 2) Don't access the .keys() 3) call addRoutingPrefixForDeploy to add a route/parent route explicitly (as is done in PathFunctionRunner.ts). 4) No server is on the network (only servers on the network can satisfy it). Path: ${JSON.stringify(path)}, network: ${parentNetwork || DEFAULT_NETWORK}`, { path, network: parentNetwork || DEFAULT_NETWORK, allSources: allSources.map(x => x.authoritySpec) });
677
+ console.error(`Want to sync a prefix which is not under an existing prefix, nor equal to a prefix. 1) The servers are down. 1.5) The servers are in an inconsistent state (one shard range added a parent path, the other didn't). 2) Don't access the .keys() 3) call addRoutingPrefixForDeploy to add a route/parent route explicitly (as is done in PathFunctionRunner.ts). Path: ${JSON.stringify(path)}`, { path, allSources: allSources.map(x => x.authoritySpec) });
701
678
  }
702
679
  return { nodes: [] };
703
680
  }
@@ -712,6 +689,8 @@ export class PathRouter {
712
689
  public static getReadyAuthority(path: string): AuthorityEntry | undefined {
713
690
  let candidates = authorityLookup.getTopologySync();
714
691
  shuffle(candidates, Math.random());
692
+ // Only use nodes that are about to shut down if nothing else matches
693
+ sort(candidates, x => authorityLookup.nodeIsShuttingDownSoon(x.nodeId) && 1 || 0);
715
694
  for (let candidate of candidates) {
716
695
  if (!candidate.isReady) continue;
717
696
  if (this.matchesAuthoritySpec(candidate.authoritySpec, path)) {
@@ -2,7 +2,6 @@ import { cacheLimited } from "socket-function/src/caching";
2
2
  import { AuthoritySpec, PathRouter } from "./PathRouter";
3
3
  import { sha256 } from "js-sha256";
4
4
  import { getPathFromStr } from "../path";
5
- import { DEFAULT_NETWORK } from "../config";
6
5
 
7
6
  function getPrefixHash(prefix: string): string {
8
7
  return Buffer.from(sha256(prefix), "hex").toString("base64").slice(0, 12);
@@ -17,102 +16,21 @@ let keySpecialIdentifier = (
17
16
  + String.fromCharCode(0xF4D7)
18
17
  );
19
18
 
20
- export function assertValidNetwork(network: string) {
21
- if (!/^[a-z]+$/.test(network)) {
22
- throw new Error(`Networks must be lowercase letters (a-z) only, was ${JSON.stringify(network)}`);
23
- }
24
- }
25
-
26
- // The route slot is either a route number ("0.12345"), a network ("dev"), or both ("dev:0.12345"). Networks are strictly lowercase letters and routes are strictly numeric, so the forms are unambiguous.
27
- function encodeRouteSlot(config: { route?: number; network?: string }): string {
28
- let { route, network } = config;
29
- let routeStr = route !== undefined && route.toString().slice(0, 7) || "";
30
- if (network && routeStr) return `${network}:${routeStr}`;
31
- if (network) return network;
32
- return routeStr;
33
- }
34
- function decodeRouteSlot(slot: string): { route?: number; network?: string } | undefined {
35
- if (!slot) return undefined;
36
- let network: string | undefined;
37
- let routeStr = slot;
38
- let colonIndex = slot.indexOf(":");
39
- if (colonIndex >= 0) {
40
- network = slot.slice(0, colonIndex);
41
- routeStr = slot.slice(colonIndex + 1);
42
- } else if (/^[a-z]+$/.test(slot)) {
43
- return { network: slot };
44
- }
45
- if (network && !/^[a-z]+$/.test(network)) return undefined;
46
- let route = parseFloat(routeStr);
47
- if (isNaN(route)) return undefined;
48
- return { route, network };
49
- }
50
-
51
- export function createRoutingOverrideKeyBase(config: {
52
- originalKey: string;
53
- // Hashed to get the route, if route is not directly provided
54
- routeKey?: string;
55
- route?: number;
56
- network?: string;
57
- // This is the prefix it has the equivalent of. We need this, so if something excludes default, it doesn't automatically get every routing overridden value. Required whenever a route (or routeKey) is set.
58
- remappedPrefix?: string;
59
- }) {
60
- let { originalKey, routeKey, route, network, remappedPrefix } = config;
61
- if (network) {
62
- assertValidNetwork(network);
63
- }
64
- if (route === undefined && routeKey !== undefined) {
65
- route = PathRouter.getSingleKeyRoute(routeKey);
66
- }
67
- if (route === undefined && !network) {
68
- throw new Error(`createRoutingOverrideKeyBase requires at least one of route, routeKey, or network`);
69
- }
70
- if (route !== undefined && !remappedPrefix) {
71
- throw new Error(`createRoutingOverrideKeyBase requires remappedPrefix when a route is set, so excludeDefault authorities can match it correctly`);
72
- }
73
- let prefixHash = remappedPrefix && getPrefixHash(remappedPrefix) || "";
74
- return keySpecialIdentifier + "!" + prefixHash + "!" + encodeRouteSlot({ route, network }) + "!" + originalKey;
75
- }
76
-
77
19
  export function createRoutingOverrideKey(config: {
78
20
  originalKey: string;
79
21
  routeKey: string;
22
+ // This is the prefix it has the equivalent of. We need this, so if something excludes default, it doesn't automatically get every routing overridden value.
80
23
  remappedPrefix: string;
81
- network?: string;
82
24
  }) {
83
- return createRoutingOverrideKeyBase(config);
84
- }
85
-
86
- /** Creates a key that routes to a specific network, but otherwise routes normally (via prefix / full path hashing). */
87
- export function createNetworkKey(config: {
88
- originalKey: string;
89
- network: string;
90
- }) {
91
- return createRoutingOverrideKeyBase({ originalKey: config.originalKey, network: config.network });
92
- }
93
-
94
- /** Rewrites the routing override part inside key (which must contain one) to be on the given network. */
95
- export function setRoutingOverrideKeyNetwork(key: string, network: string): string {
96
- assertValidNetwork(network);
97
- if (!key.startsWith(keySpecialIdentifier)) {
98
- return createNetworkKey({ originalKey: key, network });
99
- }
100
- let parts = key.split("!");
101
- if (parts.length < 4) {
102
- return createNetworkKey({ originalKey: key, network });
103
- }
104
- let slot = decodeRouteSlot(parts[2]) || {};
105
- parts[2] = encodeRouteSlot({ route: slot.route, network });
106
- return parts.join("!");
25
+ let { originalKey, routeKey, remappedPrefix } = config;
26
+ let route = PathRouter.getSingleKeyRoute(routeKey);
27
+ return keySpecialIdentifier + "!" + getPrefixHash(remappedPrefix) + "!" + route.toString().slice(0, 7) + "!" + originalKey;
107
28
  }
108
29
 
109
- export type RoutingOverride = {
30
+ export function getRoutingOverride(path: string): {
31
+ route: number;
110
32
  prefixHash: string;
111
- route?: number;
112
- network?: string;
113
- };
114
-
115
- export function getRoutingOverride(path: string): RoutingOverride | undefined {
33
+ } | undefined {
116
34
  if (!path.includes(keySpecialIdentifier)) return undefined;
117
35
  let parts = getPathFromStr(path);
118
36
  for (let part of parts) {
@@ -121,24 +39,22 @@ export function getRoutingOverride(path: string): RoutingOverride | undefined {
121
39
  }
122
40
  return undefined;
123
41
  }
124
- export function getRoutingOverridePart(part: string): RoutingOverride | undefined {
42
+ export function getRoutingOverridePart(part: string): {
43
+ prefixHash: string;
44
+ route: number;
45
+ } | undefined {
125
46
  if (!part.startsWith(keySpecialIdentifier)) return undefined;
126
47
  let parts = part.split("!");
127
48
  if (parts.length < 4) return undefined;
128
49
  let prefixHash = parts[1];
129
- let slot = decodeRouteSlot(parts[2]);
130
- if (!slot) return undefined;
50
+ let route = parseFloat(parts[2]);
51
+ if (isNaN(route)) return undefined;
131
52
  return {
132
53
  prefixHash,
133
- route: slot.route,
134
- network: slot.network,
54
+ route,
135
55
  };
136
56
  }
137
57
 
138
- export function getPathNetwork(path: string): string {
139
- return getRoutingOverride(path)?.network || DEFAULT_NETWORK;
140
- }
141
-
142
58
  export const hasPrefixHash = cacheLimited(1000 * 10,
143
59
  (config: { spec: AuthoritySpec, prefixHash: string }) => {
144
60
  let { spec, prefixHash } = config;
@@ -1,7 +1,7 @@
1
1
  import path from "path";
2
2
  import { AuthoritySpec, parsePrefixMatcher } from "./PathRouter";
3
3
  import { getPathDepth } from "../path";
4
- import { getAuthorityRange, getAuthorityExcludeDefault, getAuthorityPrefix, getNetworks } from "../config";
4
+ import { getAuthorityRange, getAuthorityExcludeDefault, getAuthorityPrefix } from "../config";
5
5
  import { getShardPrefixes } from "./ShardPrefixes";
6
6
  import { getOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
7
7
 
@@ -22,7 +22,6 @@ export async function getOurAuthoritySpec(defaultToAll?: boolean): Promise<Autho
22
22
  routeStart: 0,
23
23
  routeEnd: 1,
24
24
  prefixes: prefixMatchers,
25
- networks: getNetworks(),
26
25
  };
27
26
  }
28
27
  return undefined;
@@ -47,7 +46,6 @@ export async function getOurAuthoritySpec(defaultToAll?: boolean): Promise<Autho
47
46
  routeEnd: rangeEnd,
48
47
  prefixes: usePrefixes,
49
48
  excludeDefault: excludeDefault || undefined,
50
- networks: getNetworks(),
51
49
  };
52
50
  }
53
51
 
@@ -69,6 +67,5 @@ export async function getAllAuthoritySpec(): Promise<AuthoritySpec> {
69
67
  routeStart: 0,
70
68
  routeEnd: 1,
71
69
  prefixes: prefixMatchers,
72
- networks: getNetworks(),
73
70
  };
74
71
  }
@@ -20,8 +20,6 @@ import { debugNodeId } from "../-c-identity/IdentityController";
20
20
  import { decodeNodeId } from "sliftutils/misc/https/certs";
21
21
  import { getDomain } from "../config";
22
22
  import { decodeParentFilter, encodeParentFilter } from "./hackedPackedPathParentFiltering";
23
- import { getPathNetwork } from "./PathRouterRouteOverride";
24
- import { DEFAULT_NETWORK } from "../config";
25
23
  import { deepCloneCborx } from "../misc/cloneHelpers";
26
24
  import { removeRange } from "../rangeMath";
27
25
  import { registerShutdownHandler } from "../diagnostics/periodic";
@@ -207,9 +205,8 @@ class PathValueCommitter {
207
205
  parentSyncs: [],
208
206
  initialTriggers: { values: new Set(), parentPaths: new Set() },
209
207
  });
210
- console.error(`There are no authorities for path ${pathValue.path} (network ${getPathNetwork(pathValue.path)}). The write will be lost.`, {
208
+ console.error(`There are no authorities for path ${pathValue.path}. The write will be lost.`, {
211
209
  path: pathValue.path,
212
- network: getPathNetwork(pathValue.path),
213
210
  timeId: pathValue.time.time,
214
211
  source: pathValue.source,
215
212
  otherAuthorities,
@@ -352,7 +349,6 @@ class PathValueCommitter {
352
349
  remoteWatcher.getExistingWatchRemoteNodeId(path);
353
350
  console.warn(`Ignoring value from wrong authority. Should have been ${debugNodeId(watchingAuthorityId)}, but was received from ${debugNodeId(batch.sourceNodeId)}.`, {
354
351
  path,
355
- network: getPathNetwork(path),
356
352
  type,
357
353
  timeId: value?.time.time,
358
354
  source: value?.source,
@@ -394,7 +390,6 @@ class PathValueCommitter {
394
390
 
395
391
  console.warn(`Ignoring parent path which we aren't watching. From ${debugNodeId(batch.sourceNodeId)}.`, {
396
392
  parentPath,
397
- network: decodeParentFilter(parentPath)?.network || DEFAULT_NETWORK,
398
393
  sourceNodeId: debugNodeId(batch.sourceNodeId),
399
394
  sourceNodeThreadId: decodeNodeId(batch.sourceNodeId, getDomain())?.threadId,
400
395
  });