querysub 0.592.0 → 0.594.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.
@@ -31,7 +31,11 @@
31
31
  "WebFetch(domain:developers.cloudflare.com)",
32
32
  "PowerShell(yarn type *)",
33
33
  "PowerShell(Remove-Item -Confirm:$false \"C:\\\\Users\\\\quent\\\\.claude\\\\projects\\\\D--repos-querysub\\\\memory\\\\no-naming-new-bins.md\")",
34
- "Bash(python -)"
34
+ "Bash(python -)",
35
+ "PowerShell(yarn upreal *)",
36
+ "PowerShell($env:COMPUTERNAME; $env:USERNAME)",
37
+ "PowerShell($env:COMPUTERNAME)",
38
+ "PowerShell($env:USERNAME)"
35
39
  ]
36
40
  }
37
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.592.0",
3
+ "version": "0.594.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",
@@ -74,8 +74,8 @@
74
74
  "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
75
75
  "pako": "^2.1.0",
76
76
  "peggy": "^5.0.6",
77
- "sliftutils": "^1.7.69",
78
- "socket-function": "^1.2.27",
77
+ "sliftutils": "^1.7.74",
78
+ "socket-function": "^1.2.29",
79
79
  "terser": "^5.31.0",
80
80
  "typenode": "^6.6.1",
81
81
  "typesafecss": "^0.32.0",
package/spec.txt CHANGED
@@ -1,3 +1,15 @@
1
+ It could be it's just taking a while to get valid. Let's check it two times to see. Although if it is, that's a bug, but it's probably an easy bug to fix.
2
+
3
+ Wait, what transaction is it applying now? If it's not a valid transaction and we don't even write it, so what?
4
+
5
+ Well, we might have to restore from a snapshot if it breaks it, but I'm pretty confident we're not going to lose all of our data.
6
+
7
+ Ugh... I mean it seems like our storage is just really out of sync. Like, we're reading files and it changes every time, etc.
8
+
9
+
10
+
11
+
12
+
1
13
  Trigger values
2
14
  1) validStateComputer.ingestValuesAndValidStates
3
15
  1.5) authorityStorage.ingestValues
@@ -4,6 +4,7 @@ import { cache, cacheJSONArgsEqual } from "socket-function/src/caching";
4
4
  import { formatDateTime } from "socket-function/src/formatting/format";
5
5
  import { getDomain } from "../config";
6
6
  import { timeInMinute } from "socket-function/src/misc";
7
+ import { FindConfig } from "sliftutils/storage/IArchives";
7
8
 
8
9
  export const STORAGE_ACCOUNT = "root";
9
10
 
@@ -44,31 +45,34 @@ function archiveBuilder(bucket: string, overrides: Partial<RemoteConfigBase>) {
44
45
  const ONTARIO = `https://99-250-124-91.querysubtest.com:5234/file/${STORAGE_ACCOUNT}/${bucket}/storage/storagerouting.json`;
45
46
  const HETZNER = `https://65-109-93-113.querysubtest.com:5234/file/${STORAGE_ACCOUNT}/${bucket}/storage/storagerouting.json`;
46
47
  const BACKBLAZE = `https://f002.backblazeb2.com/file/${bucket}/storage/storagerouting.json`;
48
+
49
+ function stripeSources(urls: string[]) {
50
+ let sources: Omit<Exclude<RemoteConfigBase, string>, "validWindow">[] = [];
51
+ for (let j = 0; j < urls.length; j++) {
52
+ // Shift by j
53
+ let currentUrls = urls.slice(j).concat(urls.slice(0, j));
54
+ for (let i = 0; i < urls.length; i++) {
55
+ sources.push({ type: "remote", url: currentUrls[i], route: [i / urls.length, (i + 1) / urls.length], });
56
+ }
57
+ }
58
+ return sources;
59
+ }
60
+
61
+
47
62
  let sources = createSourceWindows(overrides, [
48
63
  {
49
- startTime: +new Date("2026-07-20 18:00:00-04:00"),
50
- sources: [
51
- { type: "remote", url: ONTARIO, route: [0.5, 1], },
52
- { type: "remote", url: HETZNER, route: [0, 0.5], },
53
- { type: "remote", url: ONTARIO, },
54
- { type: "remote", url: HETZNER, },
55
- { type: "backblaze", url: BACKBLAZE, },
56
- ]
57
- },
58
- {
59
- startTime: +new Date("2026-07-21 02:59:00-04:00"),
64
+ startTime: +new Date("2026-07-20 00:00:00-04:00"),
60
65
  sources: [
61
- { type: "remote", url: ONTARIO, route: [0, 0.5], },
62
- { type: "remote", url: HETZNER, route: [0.5, 1], },
63
- { type: "remote", url: ONTARIO, },
64
- { type: "remote", url: HETZNER, },
66
+ // NOTE: Once we add new sources, we need to add something at a new start time in the future so we have time to move the files.
67
+ // TODO: EVENTUALLY, Once we have enough data, instead of restriping, we need to bisect the data. That way we can slowly move over the data instead of trying to rewrite all of our data all at once. However, for the moment, moving all the data is probably fine. On the data center, we should be able to move terabytes of data every hour, so we should be able to handle quite a bit, especially if we have a few days to run the restriping.
68
+ ...stripeSources([ONTARIO, HETZNER]),
65
69
  { type: "backblaze", url: BACKBLAZE, },
66
70
  ]
67
71
  },
68
72
  ]);
69
73
 
70
74
  return createArchives({
71
- version: 16,
75
+ version: 18,
72
76
  sources,
73
77
  });
74
78
  }
@@ -89,8 +93,8 @@ function nestBucket(folder: string, archives: ReturnType<typeof createArchives>)
89
93
  get: (path: string, config?: GetConfig) => archives.get(folder + path, config),
90
94
  set: (path: string, data: Buffer, config?: SetConfig) => archives.set(folder + path, data, config),
91
95
  del: (path: string) => archives.del(folder + path),
92
- find: async (prefix: string, config?: { shallow?: boolean; type: "files" | "folders" }) => (await archives.find(folder + prefix, config)).map(x => x.slice(folder.length)),
93
- findInfo: async (prefix: string, config?: { shallow?: boolean; type: "files" | "folders" }) => (await archives.findInfo(folder + prefix, config)).map(x => ({ ...x, path: x.path.slice(folder.length) })),
96
+ find: async (prefix: string, config?: FindConfig) => (await archives.find(folder + prefix, config)).map(x => x.slice(folder.length)),
97
+ findInfo: async (prefix: string, config?: FindConfig) => (await archives.findInfo(folder + prefix, config)).map(x => ({ ...x, path: x.path.slice(folder.length) })),
94
98
  get2: (path: string, config?: GetConfig) => archives.get2(folder + path, config),
95
99
  setLargeFile: (config: { path: string; lastModified?: number; getNextData(): Promise<Buffer | undefined>; }) => archives.setLargeFile({ ...config, path: folder + config.path }),
96
100
  getInfo: (path: string) => archives.getInfo(folder + path),
@@ -87,7 +87,7 @@ export const isTrusted = measureWrap(async function isTrusted(machineId: string)
87
87
  }
88
88
  });
89
89
  let populateTrustedCache = lazy(async () => {
90
- let trustedMachineIds = await archives().find("");
90
+ let trustedMachineIds = await archives().find("", { fallbacks: true });
91
91
  lastArchivesTrusted = trustedMachineIds.slice();
92
92
  for (let trustedMachineId of trustedMachineIds) {
93
93
  trustedCache.add(trustedMachineId);
@@ -342,7 +342,7 @@ async function syncArchives() {
342
342
  if (isServer()) {
343
343
  // Make sure we are present
344
344
  await writeHeartbeat();
345
- let nodeIds = await archives().find("");
345
+ let nodeIds = await archives().find("", { fallbacks: true });
346
346
  console.info(`Synced node ids from archives`, { nodeIds });
347
347
  await setNodeIds(nodeIds);
348
348
  } else {
@@ -13,6 +13,7 @@ import { sha256 } from "js-sha256";
13
13
  import { rangesOverlap, removeRange } from "../rangeMath";
14
14
  import { decodeParentFilter } from "./hackedPackedPathParentFiltering";
15
15
  import { getBufferInt } from "socket-function/src/bits";
16
+ import { safeLoop } from "socket-function/src/batching";
16
17
 
17
18
  import { LOCAL_DOMAIN, LOCAL_DOMAIN_PATH } from "./PathRouterConstants";
18
19
  export { LOCAL_DOMAIN, LOCAL_DOMAIN_PATH };
@@ -320,7 +321,7 @@ export class PathRouter {
320
321
  // NOTE: Encodes all the data, even if it matches our spec or not. If you don't want this, you have to filter first
321
322
  // - If this becomes an issue we COULD filter, as we can do it quickly, but I don't think it is required, as all the present usecases prefilter anyways.
322
323
  @measureFnc
323
- public static getPathIdentifierTargets(values: PathValue[], ourSpec: AuthoritySpec): Map<string, PathValue[]> {
324
+ public static async getPathIdentifierTargets(values: PathValue[], ourSpec: AuthoritySpec): Promise<Map<string, PathValue[]>> {
324
325
  // NOTE: The file size limit is 1024 bytes. But we also have our folder, etc, so we want to add enough buffer
325
326
  // - Shorter hashes means we can store more, but there's a point when the collisions make it less useful.
326
327
  const MAX_PREFIXES_PER_FILE = 50;
@@ -333,17 +334,17 @@ export class PathRouter {
333
334
  let prefixes = ourSpec.prefixes.slice();
334
335
  sort(prefixes, x => x.prefixPartLength);
335
336
 
336
- // NOTE: If there are few enough path values for a prefix, we don't even need to calculate the routing hash.
337
+ // NOTE: If there are few enough path values for a prefix, we don't even need to calculate the routing hash.
337
338
  let byPrefix = new Map<PrefixMatcher | undefined, PathValue[]>();
338
- for (let value of values) {
339
+ await safeLoop({ data: values, name: "PathRouter.getPathIdentifierTargets|groupByPrefix" }, value => {
339
340
  let prefix = getMatchingPrefix(ourSpec, value.path);
340
- let values = byPrefix.get(prefix);
341
- if (!values) {
342
- values = [];
343
- byPrefix.set(prefix, values);
341
+ let prefixValues = byPrefix.get(prefix);
342
+ if (!prefixValues) {
343
+ prefixValues = [];
344
+ byPrefix.set(prefix, prefixValues);
344
345
  }
345
- values.push(value);
346
- }
346
+ prefixValues.push(value);
347
+ });
347
348
 
348
349
  let prefixGroups = Array.from(byPrefix.entries()).map(([prefix, values]) => ({
349
350
  prefix,
@@ -391,17 +392,17 @@ export class PathRouter {
391
392
  }
392
393
 
393
394
  let finalFiles = new Map<string, PathValue[]>();
394
- for (let group of groups) {
395
+ await safeLoop({ data: groups, name: "PathRouter.getPathIdentifierTargets|shardGroups" }, async group => {
395
396
  if (group.count < SHARD_THRESHOLD) {
396
397
  let identifier = this.encodeIdentifier({ prefixes: group.prefixes, rangeStart: 0, rangeEnd: 1 });
397
398
  finalFiles.set(identifier, group.values.flat());
398
- continue;
399
+ return;
399
400
  }
400
401
  // Split by routing hash
401
- let values = group.values.flat();
402
- let splitCount = Math.max(MIN_SHARD_FILE_COUNT, Math.ceil(values.length / TARGET_SHARD_SIZE));
402
+ let groupValues = group.values.flat();
403
+ let splitCount = Math.max(MIN_SHARD_FILE_COUNT, Math.ceil(groupValues.length / TARGET_SHARD_SIZE));
403
404
  let byRouteGroup = new Map<number, PathValue[]>();
404
- for (let value of values) {
405
+ await safeLoop({ data: groupValues, name: "PathRouter.getPathIdentifierTargets|routeValues" }, value => {
405
406
  let route = this.getRouteFull({
406
407
  path: value.path,
407
408
  spec: {
@@ -418,14 +419,14 @@ export class PathRouter {
418
419
  byRouteGroup.set(routeIndex, routeValues);
419
420
  }
420
421
  routeValues.push(value);
421
- }
422
+ });
422
423
  for (let [routeIndex, routeValues] of byRouteGroup) {
423
424
  let rangeStart = routeIndex / splitCount;
424
425
  let rangeEnd = (routeIndex + 1) / splitCount;
425
426
  let identifier = this.encodeIdentifier({ prefixes: group.prefixes, rangeStart, rangeEnd });
426
427
  finalFiles.set(identifier, routeValues);
427
428
  }
428
- }
429
+ });
429
430
 
430
431
  // NOTE: There could be a huge number of prefixes and we can't pack them all into one file because of the prefix limit, so this will write any remaining values.
431
432
  if (remainingValues.length > 0) {
@@ -5,7 +5,6 @@ import { getOwnNodeId, getOwnNodeIdAssert } from "../../-f-node-discovery/NodeDi
5
5
  import { pathValueArchives } from "../pathValueArchives";
6
6
  import { ArchiveLocker, ArchiveTransaction } from "./ArchiveLocks";
7
7
  import { copyArchiveFile, IArchives } from "sliftutils/storage/IArchives";
8
- import debugbreak from "debugbreak";
9
8
  import { formatNumber, formatTime } from "socket-function/src/formatting/format";
10
9
  import { blue, green, magenta, red } from "socket-function/src/formatting/logColors";
11
10
  import { devDebugbreak, getDomain } from "../../config";
@@ -15,7 +14,7 @@ import { getNodeId } from "socket-function/src/nodeCache";
15
14
  import { logNodeStateStats, logNodeStats } from "../../-0-hooks/hooks";
16
15
  import { parsePath, toFileNameKVP, parseFileNameKVP } from "../../misc";
17
16
  import { logDisk } from "../../diagnostics/logs/diskLogger";
18
- import { retryFunctional, runInParallel } from "socket-function/src/batching";
17
+ import { delay, retryFunctional, runInParallel } from "socket-function/src/batching";
19
18
 
20
19
  /*
21
20
  Data starts a .data files, from servers creating files.
@@ -30,13 +29,16 @@ const ARCHIVE_PROPAGATION_TIME = timeInSecond * 5;
30
29
  // a few times though, just in case the storage system was having some issues. After enough
31
30
  // tries though... we HAVE to just skip it, otherwise we will be stuck forever.
32
31
  const MAX_APPLY_TRIES = 10;
33
- const CONCURRENT_READ_COUNT = 32;
34
- const CONCURRENT_WRITE_COUNT = 16;
32
+ // Confirms used to be zero-byte files, which the storage layer now treats as deletes, wiping every historical confirmation. Locked files created before this date are assumed confirmed, as anything invalid that old would have already been cleaned up.
33
+ const ASSUME_CONFIRMED_BEFORE = Date.parse("2026-07-23T00:00:00.000Z");
34
+ // NOTE: These values have been increased as our storage servers can handle way more throughput than before because it's using WebSockets.
35
+ const CONCURRENT_READ_COUNT = 128;
36
+ const CONCURRENT_WRITE_COUNT = 128;
35
37
  const CONCURRENT_TRANSACTION_READS = 8;
36
38
 
37
39
  /** Moves by copy-then-delete (IArchives has no move). copyArchiveFile stamps the target with the source's write time, so ordering-by-writeTime survives the move. A missing source means another process already moved it, which is fine. */
38
40
  async function moveArchiveFile(from: IArchives, to: IArchives, path: string): Promise<void> {
39
- let copied = await copyArchiveFile({ from, to, path });
41
+ let copied = await copyArchiveFile({ from, to, path, noFallbacks: true });
40
42
  if (!copied) return;
41
43
  await from.del(path);
42
44
  }
@@ -61,7 +63,8 @@ export function createArchiveLocker2(config: {
61
63
  async getKeys() {
62
64
  let filesRawValues = await archiveValues.findInfo("", { type: "files" });
63
65
  let filesRawLocks = await archiveLocks.findInfo("", { type: "files" });
64
- let files: FileInfo[] = [...filesRawValues, ...filesRawLocks].map(x => ({
66
+ // Zero-byte files are deletes on new storage servers. Old storage servers still list them, so we filter here to make them behave the same.
67
+ let files: FileInfo[] = [...filesRawValues, ...filesRawLocks].filter(x => x.size > 0).map(x => ({
65
68
  file: x.path,
66
69
  createTime: x.createTime,
67
70
  size: x.size,
@@ -74,16 +77,11 @@ export function createArchiveLocker2(config: {
74
77
  logNodeStats(`archives|Created TΔ`, formatNumber, 1);
75
78
  },
76
79
  async getValue(key) {
77
- return getArchives(key).get(key);
80
+ // NOTE: I think we wanna not use fallbacks, Because we really want this to be atomic and safe.
81
+ return getArchives(key).get(key, { noFallbacks: true });
78
82
  },
79
83
  async deleteKey(key) {
80
84
  let archives = await getArchives(key);
81
- if (
82
- !await archives.getInfo(key)
83
- && !await archives.get(key)
84
- ) {
85
- return;
86
- }
87
85
  // ACTUALLY delete confirms, as they don't contain that much information, and just clutter up the recycle bin.
88
86
  // - DO archive transactions, as it might be useful to view the old transaction history.
89
87
  if (archives === archiveLocks && key.endsWith(".confirm")) {
@@ -217,7 +215,8 @@ export function createArchiveLocker2(config: {
217
215
  while (pendingFiles.length > 0) {
218
216
  let file = pendingFiles.pop()!;
219
217
  let value = await storage.getValue(file.file);
220
- readResults.set(file.file, value);
218
+ // NOTE: The way we've updated our archives is it's going to return a result unless it throws. And it shouldn't throw very often. The only reason it would return undefined is if the file was empty. After I do the deploy, get keys won't return empty files, but I need to fix this to do the deploy. So we're just going to default any empty returns to have a value. So we stop saying that files are missing and rereading infinitely.
219
+ readResults.set(file.file, value ?? Buffer.alloc(0));
221
220
  }
222
221
  }
223
222
  await Promise.all(list(CONCURRENT_READ_COUNT).map(runReadThread));
@@ -420,6 +419,19 @@ class TransactionLocker {
420
419
  }
421
420
  };
422
421
  await Promise.all(list(CONCURRENT_WRITE_COUNT).map(runThread));
422
+
423
+ // Verify the files still exist. There shouldn't be any valid case where they disappear. If they do disappear, it's our storage's fault.
424
+ let keys = await this.storage.getKeys();
425
+ let keysSet = new Set(keys.map(a => a.file));
426
+ let creates = transaction.ops.filter(a => a.type === "create") as { type: "create"; key: string; value: Buffer | undefined }[];
427
+ let missingCreates = creates.filter(a => !keysSet.has(a.key));
428
+ if (missingCreates.length > 0) {
429
+ console.error(`We are missing files that we just wrote to storage. This shouldn't be possible because the files can't be cleaned up until they get old enough, so this means our storage is broken`, {
430
+ missingCount: missingCreates.length,
431
+ firstMissing: missingCreates[0]?.key,
432
+ firstMissingSize: missingCreates[0]?.value?.length,
433
+ });
434
+ }
423
435
  }
424
436
  // Just writes the transaction (in a format readDataState can read)
425
437
  private async writeTransaction(transaction: Transaction & { seqNum: number; }): Promise<void> {
@@ -592,7 +604,7 @@ class TransactionLocker {
592
604
  if (!file.file.endsWith(".locked")) continue;
593
605
  let confirmKey = this.getConfirmKey(file.file);
594
606
  let confirmFile = existingFiles.get(confirmKey);
595
- if (confirmFile) {
607
+ if (confirmFile || file.createTime < ASSUME_CONFIRMED_BEFORE) {
596
608
  currentDataFiles.set(file.file, file);
597
609
  } else {
598
610
  uncomfirmedCount++;
@@ -627,25 +639,67 @@ class TransactionLocker {
627
639
  }
628
640
  // #endregion
629
641
 
630
- private isTransactionValid(transaction: Transaction, dataFiles: FileInfo[], rawDataFiles: FileInfo[]): boolean {
642
+ private async isTransactionValid(transaction: Transaction, dataFiles: FileInfo[], rawDataFiles: FileInfo[]): Promise<boolean> {
631
643
  let override = this.isTransactionValidOverride;
632
644
  if (override) return override(transaction, dataFiles, rawDataFiles);
633
645
  if (transaction.lockedFilesMustEqual) {
634
646
  let newLockedFiles = new Set(dataFiles.map(a => a.file).filter(a => a.endsWith(".locked")));
635
647
  let mustEqualSet = new Set(transaction.lockedFilesMustEqual);
636
- if (mustEqualSet.size !== newLockedFiles.size) return false;
637
- if (Array.from(mustEqualSet).some(a => !newLockedFiles.has(a))) return false;
648
+ let missingFiles = Array.from(mustEqualSet).filter(a => !newLockedFiles.has(a));
649
+ let extraFiles = Array.from(newLockedFiles).filter(a => !mustEqualSet.has(a));
650
+ let missingFile = missingFiles[0];
651
+ if (missingFile) {
652
+ console.error(`Missing file, cannot apply transaction`, {
653
+ missing: missingFile,
654
+ totalMissing: missingFiles.length,
655
+ totalExtra: extraFiles.length,
656
+ });
657
+ return false;
658
+ }
659
+ let extraFile = extraFiles[0];
660
+ if (extraFile) {
661
+ let extra = await this.storage.getValue(extraFile);
662
+ console.error(`Extra file, cannot apply transaction`, {
663
+ extra: extraFile,
664
+ extraLength: extra?.length,
665
+ extraFirst1024: extra?.slice(0, 1024).toString("hex"),
666
+ totalMissing: missingFiles.length,
667
+ totalExtra: extraFiles.length,
668
+ });
669
+ return false;
670
+ }
671
+ if (mustEqualSet.size !== newLockedFiles.size) {
672
+ console.error(`Transaction not applied (locked files must equal but size mismatch)`, {
673
+ mustEqualSetSize: mustEqualSet.size,
674
+ newLockedFilesSize: newLockedFiles.size,
675
+ });
676
+ return false;
677
+ }
638
678
  }
639
679
  let existingFiles = new Set(dataFiles.map(a => a.file));
640
680
  let existingRawFiles = new Set(rawDataFiles.map(a => a.file));
641
- return transaction.ops.every(a => {
642
- const type = a.type;
643
- // Create files might disappear if we hang and they get cleaned up
644
- if (type === "create") return existingRawFiles.has(a.key);
645
- if (type === "delete") return existingFiles.has(a.key);
646
- let unhandled: never = type;
647
- throw new Error(`Unhandled type: ${unhandled}`);
648
- });
681
+ let missingCreates = transaction.ops.filter(a => a.type === "create" && !existingRawFiles.has(a.key)) as { type: "create"; key: string; value: Buffer | undefined }[];
682
+ let missingDeletes = transaction.ops.filter(a => a.type === "delete" && !existingFiles.has(a.key)) as { type: "delete"; key: string }[];
683
+ if (missingCreates.length > 0 || missingDeletes.length > 0) {
684
+ console.error(`Transaction not applied (missing creates or deletes)`, {
685
+ missingCreateFirst: missingCreates[0]?.key,
686
+ missingDeleteFirst: missingDeletes[0]?.key,
687
+ missingCreateFirstSize: missingCreates[0]?.value?.length,
688
+ totalMissingCreates: missingCreates.length,
689
+ totalMissingDeletes: missingDeletes.length,
690
+ });
691
+ return false;
692
+ }
693
+ for (let op of transaction.ops) {
694
+ const type = op.type;
695
+ if (type === "create") {
696
+ } else if (type === "delete") {
697
+ } else {
698
+ let unhandled: never = type;
699
+ throw new Error(`Unhandled op type: ${unhandled}`);
700
+ }
701
+ }
702
+ return true;
649
703
  }
650
704
 
651
705
  // NOTE: If the transaction was applied long in the past, this will return false. HOWEVER, this is fine,
@@ -681,7 +735,7 @@ class TransactionLocker {
681
735
  let createCount = transaction.ops.filter(a => a.type === "create").length;
682
736
  let deleteCount = transaction.ops.filter(a => a.type === "delete").length;
683
737
  let lockedFiles = transaction.lockedFilesMustEqual?.length;
684
- console.info(`Applying transaction with ${createCount} file creates and ${deleteCount} file deletes. ${lockedFiles !== undefined && `Lock state depends on ${lockedFiles} files` || ""}`, {
738
+ console.log(`Applying transaction with ${createCount} file creates and ${deleteCount} file deletes. ${lockedFiles !== undefined && `Lock state depends on ${lockedFiles} files` || ""}`, {
685
739
  transactions: transaction.ops.map(x => JSON.stringify(x)),
686
740
  });
687
741
  logNodeStats(`archives|TΔ Apply`, formatNumber, 1);
@@ -707,9 +761,29 @@ class TransactionLocker {
707
761
  };
708
762
  await Promise.all(list(CONCURRENT_WRITE_COUNT).map(runThread));
709
763
 
710
- console.info(`Applied transaction with ${createCount} file creates and file ${deleteCount} deletes. ${lockedFiles !== undefined && `Lock state depends on ${lockedFiles} files` || ""}`, {
764
+ console.log(`Applied transaction with ${createCount} file creates and file ${deleteCount} deletes. ${lockedFiles !== undefined && `Lock state depends on ${lockedFiles} files` || ""}`, {
711
765
  transactions: transaction.ops.map(x => JSON.stringify(x)),
712
766
  });
767
+
768
+ let newKeys = await this.storage.getKeys();
769
+ let newKeysSet = new Set(newKeys.map(a => a.file));
770
+ let createFiles = transaction.ops.filter(a => a.type === "create") as { type: "create"; key: string; value: Buffer | undefined }[];
771
+ let deleteFiles = transaction.ops.filter(a => a.type === "delete") as { type: "delete"; key: string }[];
772
+ let deleteSet = new Set(deleteFiles.map(a => a.key));
773
+
774
+ let missingCreates = createFiles.filter(a => !newKeysSet.has(a.key));
775
+ let extraFiles = newKeys.filter(a => deleteSet.has(a.file));
776
+
777
+ if (missingCreates.length > 0 || extraFiles.length > 0) {
778
+ console.error(`The storage system is broken and did not actually apply our transaction changes (transaction ${transaction.source.file})`, {
779
+ missingCreateFirst: missingCreates[0]?.key,
780
+ missingDeleteFirst: extraFiles[0]?.file,
781
+ missingCreateFirstSize: missingCreates[0]?.value?.length,
782
+ missingDeleteFirstSize: extraFiles[0]?.size,
783
+ totalMissingCreates: missingCreates.length,
784
+ totalExtraFiles: extraFiles.length,
785
+ });
786
+ }
713
787
  }
714
788
 
715
789
  /** Only returns data files (no transaction files, or confirmations).
@@ -871,6 +945,13 @@ class TransactionLocker {
871
945
  this.perf.checkpoint("apply transaction");
872
946
  this.transactionAppliedCount.set(activeT.seqNum, applyCount + 1);
873
947
 
948
+ {
949
+ let dataState = await this.readDataState();
950
+ if (!this.wasTransactionApplied(activeT, dataState.confirmedDataFiles, dataState.rawDataFiles)) {
951
+ console.error(`Transaction ${activeT.seqNum} was not applied, even though we just applied it.`);
952
+ }
953
+ }
954
+
874
955
  // Run again, until we can be reasonable sure activeT isn't changing. We can be wrong,
875
956
  // though, which will just result in any transaction depending on activeT being
876
957
  // rejecting, and having to be inserted again.
@@ -882,7 +963,7 @@ class TransactionLocker {
882
963
  let dels = transaction.ops.filter(a => a.type === "delete").length;
883
964
  let creates = transaction.ops.filter(a => a.type === "create").length;
884
965
  let createBytes = transaction.ops.map(a => a.type === "create" && a.value?.length || 0).reduce((a, b) => a + b, 0);
885
- console.info(`Starting transaction with ${creates} file creates and ${dels} file deletes, ${formatNumber(createBytes)}B`, {
966
+ console.log(`Starting transaction with ${creates} file creates and ${dels} file deletes, ${formatNumber(createBytes)}B`, {
886
967
  createFilesNames: transaction.ops.filter(a => a.type === "create").map(a => a.key),
887
968
  deleteFilesNames: transaction.ops.filter(a => a.type === "delete").map(a => a.key),
888
969
  });
@@ -909,9 +990,12 @@ class TransactionLocker {
909
990
 
910
991
  while (true) {
911
992
  let beforeData = await this.getFilesBase();
912
- if (!this.isTransactionValid(transaction, beforeData.dataFiles, beforeData.rawDataFiles)) {
993
+ if (!await this.isTransactionValid(transaction, beforeData.dataFiles, beforeData.rawDataFiles)) {
913
994
  logNodeStats(`archives|TΔ Rejected`, formatNumber, 1);
914
- console.info(`Finished transaction with rejection, ${transaction.ops.length} ops`);
995
+ console.log(`Finished transaction with rejection, ${transaction.ops.length} ops`);
996
+ await delay(30 * 1000);
997
+ let valid2 = await this.isTransactionValid(transaction, beforeData.dataFiles, beforeData.rawDataFiles);
998
+ console.log(`Valid2: ${valid2}`);
915
999
  return "rejected";
916
1000
  }
917
1001
 
@@ -920,7 +1004,7 @@ class TransactionLocker {
920
1004
  let afterData = await this.getFilesBase();
921
1005
  if (this.wasTransactionApplied(transaction, afterData.dataFiles, afterData.rawDataFiles)) {
922
1006
  logNodeStats(`archives|TΔ Accepted`, formatNumber, 1);
923
- console.info(`Finished transaction with ${transaction.ops.length} ops`);
1007
+ console.log(`Finished transaction with ${transaction.ops.length} ops`);
924
1008
  return "accepted";
925
1009
  }
926
1010
  }
@@ -181,7 +181,7 @@ export class PathValueArchives {
181
181
 
182
182
  @measureFnc
183
183
  public async archiveValues(values: PathValue[]) {
184
- let parts = PathRouter.getPathIdentifierTargets(values, authorityLookup.getOurSpec());
184
+ let parts = await PathRouter.getPathIdentifierTargets(values, authorityLookup.getOurSpec());
185
185
 
186
186
  for (let [pathIdentifier, values] of parts) {
187
187
  let encodedObj = await this.encodeValuePaths(values);
@@ -316,8 +316,9 @@ export class PathValueArchives {
316
316
  let dataPath = pendingDataPaths.pop()!;
317
317
  if (readCache.has(dataPath)) continue;
318
318
  let data = await archives().get(dataPath);
319
- if (!data) continue;
320
- readCache.set(dataPath, data);
319
+ // NOTE: The way we've updated our archives is it's going to return a result unless it throws. And it shouldn't throw very often. The only reason it would return undefined is if the file was empty. After I do the deploy, get keys won't return empty files, but I need to fix this to do the deploy. So we're just going to default any empty returns to have a value. So we stop saying that files are missing and rereading infinitely.
320
+ //if (!data) continue;
321
+ readCache.set(dataPath, data || Buffer.alloc(0));
321
322
  }
322
323
  }
323
324
  await Promise.all(list(32).map(runReadThread));
@@ -359,6 +360,7 @@ export class PathValueArchives {
359
360
 
360
361
  await safeLoop({ data: dataPaths, name: blue("Load PathValues From Buffer") }, async dataFile => {
361
362
  let data = readCache.get(dataFile);
363
+ if (!data?.length) return;
362
364
  if (!data) throw new Error(`Data file ${dataFile} not in lookup which we just checked?`);
363
365
  totalSize += data.byteLength;
364
366
  fileCount++;
@@ -410,6 +412,7 @@ export class PathValueArchives {
410
412
  tooOldValues: number;
411
413
  }> {
412
414
  const { path, data } = config;
415
+ if (data.length === 0) return { values: [], rawCount: 0, tooOldValues: 0 };
413
416
  let tooOldValues = 0;
414
417
 
415
418
  let dataValues: PathValue[] = [];
@@ -423,10 +426,10 @@ export class PathValueArchives {
423
426
  skipValues: config.skipValues,
424
427
  });
425
428
  if (dataValues.length !== decodedObj.valueCount) {
426
- console.warn(`Bad archive data file at ${config.path}, Decoded count ${formatNumber(decodedObj.valueCount)} !== count in file name ${formatNumber(dataValues.length)} (${decodedObj.valueCount} !== ${dataValues.length})`);
429
+ console.warn(`Bad archive data file (size ${data.length}) at ${config.path}, Decoded count ${formatNumber(decodedObj.valueCount)} !== count in file name ${formatNumber(dataValues.length)} (${decodedObj.valueCount} !== ${dataValues.length})`);
427
430
  }
428
431
  } catch (e: any) {
429
- console.warn(`Bad archive data file at ${config.path}, error: ${e.stack}`);
432
+ console.warn(`Bad archive data file (size ${data.length}) at ${config.path}, error: ${e.stack}`);
430
433
  }
431
434
  let rawCount = dataValues.length;
432
435
 
@@ -250,7 +250,7 @@ export async function runArchiveMover(config: {
250
250
 
251
251
 
252
252
  for (let [key, values] of Object.entries(result.newValues)) {
253
- let targets = PathRouter.getPathIdentifierTargets(values, authority);
253
+ let targets = await PathRouter.getPathIdentifierTargets(values, authority);
254
254
  for (let [target, values] of targets) {
255
255
  await addValues(target + "/", values, key);
256
256
  }
@@ -99,7 +99,7 @@ async function runGenesisJoinIteration(config?: { force?: boolean }) {
99
99
  transaction.deleteFiles.push(file);
100
100
  }
101
101
 
102
- let targets = PathRouter.getPathIdentifierTargets(allCombinedValues, authoritySpec);
102
+ let targets = await PathRouter.getPathIdentifierTargets(allCombinedValues, authoritySpec);
103
103
  for (let [target, values] of targets) {
104
104
 
105
105
  let dataObj = await pathValueArchives.encodeValuePaths(values, {
@@ -6,7 +6,7 @@ import { sort, timeInSecond } from "socket-function/src/misc";
6
6
  import preact from "preact";
7
7
  import { Querysub } from "../../4-querysub/Querysub";
8
8
  import { listServerBuckets, clearServerWriteStats, activateServerBucket, getServerActiveBucket } from "sliftutils/storage/remoteStorage/createArchives";
9
- import type { ServerBucketInfo, BucketDiskInfo, BucketWriteStats, ActiveBucketInfo } from "sliftutils/storage/remoteStorage/storageServerState";
9
+ import type { ServerBucketInfo, BucketWriteStats, ActiveBucketInfo } from "sliftutils/storage/remoteStorage/storageServerState";
10
10
  import type { ArchivesConfig, SyncActivity, RemoteConfig, HostedConfig } from "sliftutils/storage/IArchives";
11
11
  import { parseHostedUrl } from "sliftutils/storage/remoteStorage/remoteConfig";
12
12
  import { UsageBar, getUsageThresholds } from "../../library-components/UsageBar";
@@ -19,6 +19,7 @@ import { Table } from "../../5-diagnostics/Table";
19
19
  import { RouteConfigView } from "./RouteConfigView";
20
20
  import { Tag, SourceTag, getSourceTags } from "./Tag";
21
21
  import { STORAGE_COMMAND_PREFIX } from "../serviceCategories";
22
+ import { BucketDiskInfo } from "sliftutils/storage/remoteStorage/bucketDisk";
22
23
 
23
24
  module.hotreload = true;
24
25
 
@@ -48,7 +48,7 @@ let pendingDiscordNotifications = new Map<string, {
48
48
  errorCount: number;
49
49
  isNew: boolean;
50
50
  }>();
51
- async function getSuppressionEntries(): Promise<SuppressionEntry[]> {
51
+ export async function getSuppressionEntries(): Promise<SuppressionEntry[]> {
52
52
  return await suppression.values();
53
53
  }
54
54
 
@@ -295,14 +295,12 @@ export function getSyncedController<T extends {
295
295
  });
296
296
  // We have to wait until we actually commit before making the call. Otherwise, if this commit is rejected because we need to do synchronized values, we'll call the function twice.
297
297
  let fnc = controller.nodes[nodeId][fncName] as any;
298
- Querysub.onCommitFinished(() => {
299
- // Doesn't on commit finished also implicitly mean we're all synced? Pretty sure that isAll synced is making things break.
300
- //if (Querysub.isAllSynced()) {
298
+ // NOTE: We can't do on commit finish because we later have a trigger on promise finish for our own promise. And if we don't start the promise call until we finish the commit, And we can't finish the commit until we start the promise call, it'll never finish. So... The best we can do is just check for if everything's synced at this current point.
299
+ if (Querysub.isAllSynced()) {
301
300
  void Promise.resolve().then(() => {
302
301
  doPromiseCall();
303
302
  });
304
- //}
305
- });
303
+ }
306
304
  function doPromiseCall() {
307
305
  let promise = fnc(...args) as Promise<unknown>;
308
306
  promiseObjBase.resolve(promise);
@@ -15,7 +15,7 @@ async function main() {
15
15
 
16
16
  let trustedMachineIds = await getTrustedMachineIds();
17
17
 
18
- let { setTrustedMachines } = await import("sliftutils/storage/remoteStorage/storageServerState");
18
+ let { setTrustedMachines } = await import("sliftutils/storage/remoteStorage/serverConfig");
19
19
  await setTrustedMachines({ account: "root", machineIds: trustedMachineIds });
20
20
  console.log(`Synchronized ${trustedMachineIds.length} trusted machines`);
21
21