querysub 0.646.0 → 0.647.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.646.0",
3
+ "version": "0.647.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",
@@ -12,22 +12,26 @@ import { getClientNodeId, getNodeId, getNodeIdDomain, getNodeIdIP, getNodeIdLoca
12
12
  import { decodeNodeId, getCommonName, getIdentityCA, getMachineId, getOwnMachineId, getPublicIdentifier, getThreadKeyCert, parseCert, sign, validateCertificate, verify } from "sliftutils/misc/https/certs";
13
13
  import { getShortNumber } from "socket-function/src/bits";
14
14
  import { measureBlock, measureFnc, measureWrap } from "socket-function/src/profiling/measure";
15
- import { timeoutToError } from "../errors";
15
+ import { logErrors, timeoutToError } from "../errors";
16
16
  import { delay } from "socket-function/src/batching";
17
17
  import { formatTime } from "socket-function/src/formatting/format";
18
18
  import { waitForFirstTimeSync } from "socket-function/time/trueTimeShim";
19
19
  import { red } from "socket-function/src/formatting/logColors";
20
20
  import { isNode } from "typesafecss";
21
21
  import { areNodeIdsEqual, getOwnNodeId, getOwnThreadId } from "../-f-node-discovery/NodeDiscovery";
22
- import { timeInMinute, isIpDomain } from "socket-function/src/misc";
22
+ import { timeInMinute, isIpDomain, PromiseObj } from "socket-function/src/misc";
23
23
  import { isClient, isServer } from "../config2";
24
24
  import { getDomain } from "../config";
25
25
 
26
26
  // NOTE: This used to be small, but we cache this, so it would mean a node on startup would time out, and then we would refuse to talk to it ever again. So... this can't be small
27
27
  const MAX_CHANGE_IDENTITY_TIMEOUT = timeInMinute * 5;
28
+ // Such a large timeout because sometimes startup can be really slow, such as if our storage system has to fall back (as the storage system does retries, etc).
29
+ const MOUNT_WAIT_TIMEOUT = timeInMinute * 3;
28
30
 
29
31
  let callerInfo = new Map<CallerContext, {
30
- reconnectNodeId: string | undefined;
32
+ reconnectNodeId: string | Promise<string | Error> | undefined;
33
+ resolveReconnectNodeId?: (value: string | Error) => void;
34
+ certCommonName: string;
31
35
  machineId: string;
32
36
  cert: CertInfo;
33
37
  pubKey: Buffer;
@@ -49,25 +53,40 @@ export function IdentityController_getReconnectNodeId(callerContext: CallerConte
49
53
  if (allowUndefined) return undefined;
50
54
  throw new Error(callerInfoErrorString);
51
55
  }
52
- return info.reconnectNodeId;
56
+ let value = info.reconnectNodeId;
57
+ if (typeof value !== "string") return undefined;
58
+ return value;
53
59
  }
54
- export function IdentityController_getReconnectNodeIdAssert(callerContext: CallerContext): string {
60
+ export async function IdentityController_getReconnectNodeIdAssert(callerContext: CallerContext): Promise<string> {
55
61
  if (!isClientNodeId(callerContext.nodeId)) {
56
62
  return callerContext.nodeId;
57
63
  }
58
- let reconnectId = IdentityController_getReconnectNodeId(callerContext);
59
- if (!reconnectId) {
64
+ let info = callerInfo.get(callerContext);
65
+ if (!info) {
66
+ throw new Error(callerInfoErrorString);
67
+ }
68
+ let valueBase = info.reconnectNodeId;
69
+ let value: string | Error | undefined;
70
+ if (valueBase instanceof Promise) {
71
+ value = await valueBase;
72
+ } else {
73
+ value = valueBase;
74
+ }
75
+ if (value instanceof Error) {
76
+ throw value;
77
+ }
78
+ if (!value) {
60
79
  console.error(`Caller did not mount before connecting. This call requires the caller to be listening.`, {
61
80
  callerNodeId: callerContext.nodeId,
62
81
  });
63
82
  throw new Error(`Caller did not mount before connecting. This call requires the caller to be listening.`);
64
83
  }
65
- return reconnectId;
84
+ return value;
66
85
  }
67
86
  export function IdentityController_getSecureIP(callerContext: CallerContext): string {
68
87
  return getNodeIdIP(callerContext.nodeId);
69
88
  }
70
- export function IdentityController_getCurrentReconnectNodeIdAssert(): string {
89
+ export function IdentityController_getCurrentReconnectNodeIdAssert(): Promise<string> {
71
90
  return IdentityController_getReconnectNodeIdAssert(SocketFunction.getCaller());
72
91
  }
73
92
  export function IdentityController_getCurrentReconnectNodeId(): string | undefined {
@@ -76,7 +95,9 @@ export function IdentityController_getCurrentReconnectNodeId(): string | undefin
76
95
 
77
96
  export function debugNodeId(nodeId: string) {
78
97
  let info = Array.from(callerInfo.entries()).find(x => x[0].nodeId === nodeId);
79
- return info?.[1].reconnectNodeId || nodeId;
98
+ let value = info?.[1].reconnectNodeId;
99
+ if (typeof value !== "string") return nodeId;
100
+ return value;
80
101
  };
81
102
  (globalThis as any).debugNodeId = debugNodeId;
82
103
 
@@ -177,12 +198,21 @@ class IdentityControllerBase {
177
198
  // Verify we even trust the issuer/cert pair
178
199
  validateCertificate(getDomain(), payload.cert, payload.certIssuer);
179
200
 
180
- let reconnectNodeId: string | undefined;
201
+ // NOTE: We use the common name, and not getNodeIdIP... because anything connecting will need
202
+ // to use HTTPS, so connecting over the IP won't work! (unless they turn off certificate validation,
203
+ // which we shouldn't do...)
204
+ let certCommonName = getCommonName(payload.cert);
205
+ let reconnectNodeId: string | Promise<string | Error> | undefined;
206
+ let resolveReconnectNodeId: ((value: string | Error) => void) | undefined;
181
207
  if (payload.mountedPort) {
182
- // NOTE: We use the common name, and not getNodeIdIP... because anything connecting will need
183
- // to use HTTPS, so connecting over the IP won't work! (unless they turn off certificate validation,
184
- // which we shouldn't do...)
185
- reconnectNodeId = getNodeId(getCommonName(payload.cert), payload.mountedPort);
208
+ reconnectNodeId = getNodeId(certCommonName, payload.mountedPort);
209
+ } else {
210
+ let pending = new PromiseObj<string | Error>();
211
+ reconnectNodeId = pending.promise;
212
+ resolveReconnectNodeId = pending.resolve;
213
+ setTimeout(() => {
214
+ pending.resolve(new Error(`Caller did not mount within ${formatTime(MOUNT_WAIT_TIMEOUT)} of connecting. This call requires the caller to be listening.`));
215
+ }, MOUNT_WAIT_TIMEOUT);
186
216
  }
187
217
 
188
218
  let pubKey = getPublicIdentifier(payload.cert);
@@ -191,7 +221,9 @@ class IdentityControllerBase {
191
221
  callerInfo.set(caller, {
192
222
  cert: { certPEM: payload.cert, issuerPEM: payload.certIssuer },
193
223
  machineId,
224
+ certCommonName,
194
225
  reconnectNodeId,
226
+ resolveReconnectNodeId,
195
227
  pubKey,
196
228
  pubKeyShort: getShortNumber(pubKey),
197
229
  });
@@ -206,13 +238,28 @@ class IdentityControllerBase {
206
238
  });
207
239
 
208
240
  SocketFunction.onNextDisconnect(caller.nodeId, () => {
209
- // NOTE: I don't really see any purpose of deleting from caller info. I don't think we're going to run out of memory because of too many callers authenticating.
241
+ // NOTE: I don't really see any purpose of deleting from caller info. I don't think we're going to run out of memory because of too many callers authenticating.
210
242
  // However, logging here is useful as it allows us to complete the life cycle so we know how long a client was connected for.
211
243
  console.info(`Disconnected client`, {
212
244
  clientId: caller.nodeId,
213
245
  });
214
246
  });
215
247
  }
248
+
249
+ @measureFnc
250
+ public async setMountedPort(mountedPort: number) {
251
+ const caller = SocketFunction.getCaller();
252
+ let info = callerInfo.get(caller);
253
+ if (!info) throw new Error(callerInfoErrorString);
254
+ let reconnectNodeId = getNodeId(info.certCommonName, mountedPort);
255
+ info.reconnectNodeId = reconnectNodeId;
256
+ info.resolveReconnectNodeId?.(reconnectNodeId);
257
+ console.info(`Caller mounted after connecting, set reconnect node id`, {
258
+ clientId: caller.nodeId,
259
+ reconnectNodeId,
260
+ mountedPort,
261
+ });
262
+ }
216
263
  }
217
264
 
218
265
  const IdentityController = SocketFunction.register(
@@ -227,6 +274,7 @@ const IdentityController = SocketFunction.register(
227
274
  // noClientHooks: true,
228
275
  // noDefaultHooks: true,
229
276
  },
277
+ setMountedPort: {},
230
278
  })
231
279
  );
232
280
 
@@ -250,6 +298,19 @@ const changeIdentityOnce = cacheWeak(async function changeIdentityOnce(connectio
250
298
  IdentityController.nodes[nodeId].changeIdentity(signature, payload),
251
299
  () => new Error(`Timeout calling changeIdentity for ${nodeId}`)
252
300
  );
301
+ if (isServer() && !payload.mountedPort) {
302
+ logErrors((async () => {
303
+ await SocketFunction.mountPromise;
304
+ let mountedPort = getNodeIdLocation(SocketFunction.mountedNodeId)?.port;
305
+ if (!mountedPort) return;
306
+ console.info(`Mounted after connecting to ${nodeId}, sending mounted port ${mountedPort}`);
307
+ await timeoutToError(
308
+ MAX_CHANGE_IDENTITY_TIMEOUT,
309
+ IdentityController.nodes[nodeId].setMountedPort(mountedPort),
310
+ () => new Error(`Timeout calling setMountedPort for ${nodeId}`)
311
+ );
312
+ })());
313
+ }
253
314
  });
254
315
  let startupTime = Date.now();
255
316
  async function identityHook(context: ClientHookContext) {
@@ -180,7 +180,7 @@ class AuthorityLookup {
180
180
  isReady: boolean;
181
181
  scheduledShutdownTime?: number;
182
182
  }> {
183
- let nodeId = IdentityController_getCurrentReconnectNodeIdAssert();
183
+ let nodeId = await IdentityController_getCurrentReconnectNodeIdAssert();
184
184
  this.updatePaths(nodeId, config.spec, config.isReady, config.scheduledShutdownTime);
185
185
  console.info(`Received network sync, returning our data`, {
186
186
  nodeId,
@@ -211,7 +211,7 @@ class AuthorityLookup {
211
211
  }, stopObj);
212
212
 
213
213
  const onNodeRemoved = () => {
214
- this.topology.nodes.delete(nodeId);
214
+ this.removeNode(nodeId, "node removed from node discovery");
215
215
  stopObj.stop = true;
216
216
  this.connectTo.clear(nodeId);
217
217
  this.disconnectWatch.clear(nodeId);
@@ -230,15 +230,20 @@ class AuthorityLookup {
230
230
  let onDisconnect = () => {
231
231
  SocketFunction.onNextDisconnect(nodeId, onDisconnect, "iKnowThatServerNodeIdsMayReconnect_andIHandleReconnections");
232
232
  // NOTE: Will be re-added once we receive an updatePaths call (or the node will be removed by NodeDiscovery, in which case we will be entirely cleaned up).
233
- this.topology.nodes.delete(nodeId);
233
+ this.removeNode(nodeId, "socket disconnected");
234
234
  };
235
235
  SocketFunction.onNextDisconnect(nodeId, onDisconnect, "iKnowThatServerNodeIdsMayReconnect_andIHandleReconnections");
236
236
  });
237
+ private removeNode(nodeId: string, reason: string) {
238
+ if (!this.topology.nodes.has(nodeId)) return;
239
+ this.topology.nodes.delete(nodeId);
240
+ console.log(`Removed authority topology entry for ${nodeId} (${this.topology.nodes.size} entries remaining). Reason: ${reason}`);
241
+ }
237
242
  private async syncNodeNow(nodeId: string) {
238
243
  try {
239
244
  await this.syncNodeNowBase(nodeId);
240
- } catch {
241
- this.topology.nodes.delete(nodeId);
245
+ } catch (e: any) {
246
+ this.removeNode(nodeId, `sync failed: ${String(e?.stack || e)}`);
242
247
  }
243
248
  }
244
249
 
@@ -210,6 +210,7 @@ class PathValueCommitter {
210
210
  timeId: pathValue.time.time,
211
211
  source: pathValue.source,
212
212
  otherAuthorities,
213
+ rawTopology: authorityLookup.getTopologySync(),
213
214
  });
214
215
  continue;
215
216
  }
@@ -66,7 +66,8 @@ class StorageLogsControllerBase {
66
66
  let stores: ServerPathValue["stores"] = [];
67
67
  try {
68
68
  let routing = await controller.get2({ account, bucketName, path: ROUTING_FILE, sourceConfig: fabricatedSource, internal: true });
69
- let sources = routing && parseRoutingData(routing.data).sources.map(normalizeSource) || [];
69
+ let routingParsed = routing && parseRoutingData(routing.data);
70
+ let sources = routingParsed && routingParsed.sources.map(normalizeSource) || [];
70
71
  let seenNames = new Set<string>();
71
72
  for (let source of sources) {
72
73
  if (source.type !== "remote") continue;