querysub 0.592.0 → 0.593.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.593.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.72",
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,24 +45,27 @@ 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
  },
@@ -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,6 +29,8 @@ 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;
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");
33
34
  const CONCURRENT_READ_COUNT = 32;
34
35
  const CONCURRENT_WRITE_COUNT = 16;
35
36
  const CONCURRENT_TRANSACTION_READS = 8;
@@ -61,7 +62,8 @@ export function createArchiveLocker2(config: {
61
62
  async getKeys() {
62
63
  let filesRawValues = await archiveValues.findInfo("", { type: "files" });
63
64
  let filesRawLocks = await archiveLocks.findInfo("", { type: "files" });
64
- let files: FileInfo[] = [...filesRawValues, ...filesRawLocks].map(x => ({
65
+ // 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.
66
+ let files: FileInfo[] = [...filesRawValues, ...filesRawLocks].filter(x => x.size > 0).map(x => ({
65
67
  file: x.path,
66
68
  createTime: x.createTime,
67
69
  size: x.size,
@@ -78,12 +80,6 @@ export function createArchiveLocker2(config: {
78
80
  },
79
81
  async deleteKey(key) {
80
82
  let archives = await getArchives(key);
81
- if (
82
- !await archives.getInfo(key)
83
- && !await archives.get(key)
84
- ) {
85
- return;
86
- }
87
83
  // ACTUALLY delete confirms, as they don't contain that much information, and just clutter up the recycle bin.
88
84
  // - DO archive transactions, as it might be useful to view the old transaction history.
89
85
  if (archives === archiveLocks && key.endsWith(".confirm")) {
@@ -217,7 +213,8 @@ export function createArchiveLocker2(config: {
217
213
  while (pendingFiles.length > 0) {
218
214
  let file = pendingFiles.pop()!;
219
215
  let value = await storage.getValue(file.file);
220
- readResults.set(file.file, value);
216
+ // 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.
217
+ readResults.set(file.file, value ?? Buffer.alloc(0));
221
218
  }
222
219
  }
223
220
  await Promise.all(list(CONCURRENT_READ_COUNT).map(runReadThread));
@@ -420,6 +417,19 @@ class TransactionLocker {
420
417
  }
421
418
  };
422
419
  await Promise.all(list(CONCURRENT_WRITE_COUNT).map(runThread));
420
+
421
+ // 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.
422
+ let keys = await this.storage.getKeys();
423
+ let keysSet = new Set(keys.map(a => a.file));
424
+ let creates = transaction.ops.filter(a => a.type === "create") as { type: "create"; key: string; value: Buffer | undefined }[];
425
+ let missingCreates = creates.filter(a => !keysSet.has(a.key));
426
+ if (missingCreates.length > 0) {
427
+ 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`, {
428
+ missingCount: missingCreates.length,
429
+ firstMissing: missingCreates[0]?.key,
430
+ firstMissingSize: missingCreates[0]?.value?.length,
431
+ });
432
+ }
423
433
  }
424
434
  // Just writes the transaction (in a format readDataState can read)
425
435
  private async writeTransaction(transaction: Transaction & { seqNum: number; }): Promise<void> {
@@ -592,7 +602,7 @@ class TransactionLocker {
592
602
  if (!file.file.endsWith(".locked")) continue;
593
603
  let confirmKey = this.getConfirmKey(file.file);
594
604
  let confirmFile = existingFiles.get(confirmKey);
595
- if (confirmFile) {
605
+ if (confirmFile || file.createTime < ASSUME_CONFIRMED_BEFORE) {
596
606
  currentDataFiles.set(file.file, file);
597
607
  } else {
598
608
  uncomfirmedCount++;
@@ -627,25 +637,67 @@ class TransactionLocker {
627
637
  }
628
638
  // #endregion
629
639
 
630
- private isTransactionValid(transaction: Transaction, dataFiles: FileInfo[], rawDataFiles: FileInfo[]): boolean {
640
+ private async isTransactionValid(transaction: Transaction, dataFiles: FileInfo[], rawDataFiles: FileInfo[]): Promise<boolean> {
631
641
  let override = this.isTransactionValidOverride;
632
642
  if (override) return override(transaction, dataFiles, rawDataFiles);
633
643
  if (transaction.lockedFilesMustEqual) {
634
644
  let newLockedFiles = new Set(dataFiles.map(a => a.file).filter(a => a.endsWith(".locked")));
635
645
  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;
646
+ let missingFiles = Array.from(mustEqualSet).filter(a => !newLockedFiles.has(a));
647
+ let extraFiles = Array.from(newLockedFiles).filter(a => !mustEqualSet.has(a));
648
+ let missingFile = missingFiles[0];
649
+ if (missingFile) {
650
+ console.error(`Missing file, cannot apply transaction`, {
651
+ missing: missingFile,
652
+ totalMissing: missingFiles.length,
653
+ totalExtra: extraFiles.length,
654
+ });
655
+ return false;
656
+ }
657
+ let extraFile = extraFiles[0];
658
+ if (extraFile) {
659
+ let extra = await this.storage.getValue(extraFile);
660
+ console.error(`Extra file, cannot apply transaction`, {
661
+ extra: extraFile,
662
+ extraLength: extra?.length,
663
+ extraFirst1024: extra?.slice(0, 1024).toString("hex"),
664
+ totalMissing: missingFiles.length,
665
+ totalExtra: extraFiles.length,
666
+ });
667
+ return false;
668
+ }
669
+ if (mustEqualSet.size !== newLockedFiles.size) {
670
+ console.error(`Transaction not applied (locked files must equal but size mismatch)`, {
671
+ mustEqualSetSize: mustEqualSet.size,
672
+ newLockedFilesSize: newLockedFiles.size,
673
+ });
674
+ return false;
675
+ }
638
676
  }
639
677
  let existingFiles = new Set(dataFiles.map(a => a.file));
640
678
  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
- });
679
+ let missingCreates = transaction.ops.filter(a => a.type === "create" && !existingRawFiles.has(a.key)) as { type: "create"; key: string; value: Buffer | undefined }[];
680
+ let missingDeletes = transaction.ops.filter(a => a.type === "delete" && !existingFiles.has(a.key)) as { type: "delete"; key: string }[];
681
+ if (missingCreates.length > 0 || missingDeletes.length > 0) {
682
+ console.error(`Transaction not applied (missing creates or deletes)`, {
683
+ missingCreateFirst: missingCreates[0]?.key,
684
+ missingDeleteFirst: missingDeletes[0]?.key,
685
+ missingCreateFirstSize: missingCreates[0]?.value?.length,
686
+ totalMissingCreates: missingCreates.length,
687
+ totalMissingDeletes: missingDeletes.length,
688
+ });
689
+ return false;
690
+ }
691
+ for (let op of transaction.ops) {
692
+ const type = op.type;
693
+ if (type === "create") {
694
+ } else if (type === "delete") {
695
+ } else {
696
+ let unhandled: never = type;
697
+ throw new Error(`Unhandled op type: ${unhandled}`);
698
+ }
699
+ }
700
+ return true;
649
701
  }
650
702
 
651
703
  // NOTE: If the transaction was applied long in the past, this will return false. HOWEVER, this is fine,
@@ -681,7 +733,7 @@ class TransactionLocker {
681
733
  let createCount = transaction.ops.filter(a => a.type === "create").length;
682
734
  let deleteCount = transaction.ops.filter(a => a.type === "delete").length;
683
735
  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` || ""}`, {
736
+ console.log(`Applying transaction with ${createCount} file creates and ${deleteCount} file deletes. ${lockedFiles !== undefined && `Lock state depends on ${lockedFiles} files` || ""}`, {
685
737
  transactions: transaction.ops.map(x => JSON.stringify(x)),
686
738
  });
687
739
  logNodeStats(`archives|TΔ Apply`, formatNumber, 1);
@@ -707,9 +759,29 @@ class TransactionLocker {
707
759
  };
708
760
  await Promise.all(list(CONCURRENT_WRITE_COUNT).map(runThread));
709
761
 
710
- console.info(`Applied transaction with ${createCount} file creates and file ${deleteCount} deletes. ${lockedFiles !== undefined && `Lock state depends on ${lockedFiles} files` || ""}`, {
762
+ console.log(`Applied transaction with ${createCount} file creates and file ${deleteCount} deletes. ${lockedFiles !== undefined && `Lock state depends on ${lockedFiles} files` || ""}`, {
711
763
  transactions: transaction.ops.map(x => JSON.stringify(x)),
712
764
  });
765
+
766
+ let newKeys = await this.storage.getKeys();
767
+ let newKeysSet = new Set(newKeys.map(a => a.file));
768
+ let createFiles = transaction.ops.filter(a => a.type === "create") as { type: "create"; key: string; value: Buffer | undefined }[];
769
+ let deleteFiles = transaction.ops.filter(a => a.type === "delete") as { type: "delete"; key: string }[];
770
+ let deleteSet = new Set(deleteFiles.map(a => a.key));
771
+
772
+ let missingCreates = createFiles.filter(a => !newKeysSet.has(a.key));
773
+ let extraFiles = newKeys.filter(a => deleteSet.has(a.file));
774
+
775
+ if (missingCreates.length > 0 || extraFiles.length > 0) {
776
+ console.error(`The storage system is broken and did not actually apply our transaction changes (transaction ${transaction.source.file})`, {
777
+ missingCreateFirst: missingCreates[0]?.key,
778
+ missingDeleteFirst: extraFiles[0]?.file,
779
+ missingCreateFirstSize: missingCreates[0]?.value?.length,
780
+ missingDeleteFirstSize: extraFiles[0]?.size,
781
+ totalMissingCreates: missingCreates.length,
782
+ totalExtraFiles: extraFiles.length,
783
+ });
784
+ }
713
785
  }
714
786
 
715
787
  /** Only returns data files (no transaction files, or confirmations).
@@ -871,6 +943,13 @@ class TransactionLocker {
871
943
  this.perf.checkpoint("apply transaction");
872
944
  this.transactionAppliedCount.set(activeT.seqNum, applyCount + 1);
873
945
 
946
+ {
947
+ let dataState = await this.readDataState();
948
+ if (!this.wasTransactionApplied(activeT, dataState.confirmedDataFiles, dataState.rawDataFiles)) {
949
+ console.error(`Transaction ${activeT.seqNum} was not applied, even though we just applied it.`);
950
+ }
951
+ }
952
+
874
953
  // Run again, until we can be reasonable sure activeT isn't changing. We can be wrong,
875
954
  // though, which will just result in any transaction depending on activeT being
876
955
  // rejecting, and having to be inserted again.
@@ -882,7 +961,7 @@ class TransactionLocker {
882
961
  let dels = transaction.ops.filter(a => a.type === "delete").length;
883
962
  let creates = transaction.ops.filter(a => a.type === "create").length;
884
963
  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`, {
964
+ console.log(`Starting transaction with ${creates} file creates and ${dels} file deletes, ${formatNumber(createBytes)}B`, {
886
965
  createFilesNames: transaction.ops.filter(a => a.type === "create").map(a => a.key),
887
966
  deleteFilesNames: transaction.ops.filter(a => a.type === "delete").map(a => a.key),
888
967
  });
@@ -909,9 +988,12 @@ class TransactionLocker {
909
988
 
910
989
  while (true) {
911
990
  let beforeData = await this.getFilesBase();
912
- if (!this.isTransactionValid(transaction, beforeData.dataFiles, beforeData.rawDataFiles)) {
991
+ if (!await this.isTransactionValid(transaction, beforeData.dataFiles, beforeData.rawDataFiles)) {
913
992
  logNodeStats(`archives|TΔ Rejected`, formatNumber, 1);
914
- console.info(`Finished transaction with rejection, ${transaction.ops.length} ops`);
993
+ console.log(`Finished transaction with rejection, ${transaction.ops.length} ops`);
994
+ await delay(30 * 1000);
995
+ let valid2 = await this.isTransactionValid(transaction, beforeData.dataFiles, beforeData.rawDataFiles);
996
+ console.log(`Valid2: ${valid2}`);
915
997
  return "rejected";
916
998
  }
917
999
 
@@ -920,7 +1002,7 @@ class TransactionLocker {
920
1002
  let afterData = await this.getFilesBase();
921
1003
  if (this.wasTransactionApplied(transaction, afterData.dataFiles, afterData.rawDataFiles)) {
922
1004
  logNodeStats(`archives|TΔ Accepted`, formatNumber, 1);
923
- console.info(`Finished transaction with ${transaction.ops.length} ops`);
1005
+ console.log(`Finished transaction with ${transaction.ops.length} ops`);
924
1006
  return "accepted";
925
1007
  }
926
1008
  }
@@ -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, {
@@ -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