querysub 0.2.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 (169) hide show
  1. package/.dependency-cruiser.js +304 -0
  2. package/.eslintrc.js +51 -0
  3. package/.github/copilot-instructions.md +1 -0
  4. package/.vscode/settings.json +25 -0
  5. package/bin/deploy.js +4 -0
  6. package/bin/function.js +4 -0
  7. package/bin/server.js +4 -0
  8. package/costsBenefits.txt +112 -0
  9. package/deploy.ts +3 -0
  10. package/inject.ts +1 -0
  11. package/package.json +60 -0
  12. package/prompts.txt +54 -0
  13. package/spec.txt +820 -0
  14. package/src/-a-archives/archiveCache.ts +913 -0
  15. package/src/-a-archives/archives.ts +148 -0
  16. package/src/-a-archives/archivesBackBlaze.ts +792 -0
  17. package/src/-a-archives/archivesDisk.ts +418 -0
  18. package/src/-a-archives/copyLocalToBackblaze.ts +24 -0
  19. package/src/-a-auth/certs.ts +517 -0
  20. package/src/-a-auth/der.ts +122 -0
  21. package/src/-a-auth/ed25519.ts +1015 -0
  22. package/src/-a-auth/node-forge-ed25519.d.ts +17 -0
  23. package/src/-b-authorities/dnsAuthority.ts +203 -0
  24. package/src/-b-authorities/emailAuthority.ts +57 -0
  25. package/src/-c-identity/IdentityController.ts +200 -0
  26. package/src/-d-trust/NetworkTrust2.ts +150 -0
  27. package/src/-e-certs/EdgeCertController.ts +288 -0
  28. package/src/-e-certs/certAuthority.ts +192 -0
  29. package/src/-f-node-discovery/NodeDiscovery.ts +543 -0
  30. package/src/-g-core-values/NodeCapabilities.ts +134 -0
  31. package/src/-g-core-values/oneTimeForward.ts +91 -0
  32. package/src/-h-path-value-serialize/PathValueSerializer.ts +769 -0
  33. package/src/-h-path-value-serialize/stringSerializer.ts +176 -0
  34. package/src/0-path-value-core/LoggingClient.tsx +24 -0
  35. package/src/0-path-value-core/NodePathAuthorities.ts +978 -0
  36. package/src/0-path-value-core/PathController.ts +1 -0
  37. package/src/0-path-value-core/PathValueCommitter.ts +565 -0
  38. package/src/0-path-value-core/PathValueController.ts +231 -0
  39. package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +154 -0
  40. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +820 -0
  41. package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +180 -0
  42. package/src/0-path-value-core/debugLogs.ts +90 -0
  43. package/src/0-path-value-core/pathValueArchives.ts +483 -0
  44. package/src/0-path-value-core/pathValueCore.ts +2217 -0
  45. package/src/1-path-client/RemoteWatcher.ts +558 -0
  46. package/src/1-path-client/pathValueClientWatcher.ts +702 -0
  47. package/src/2-proxy/PathValueProxyWatcher.ts +1857 -0
  48. package/src/2-proxy/archiveMoveHarness.ts +376 -0
  49. package/src/2-proxy/garbageCollection.ts +753 -0
  50. package/src/2-proxy/pathDatabaseProxyBase.ts +37 -0
  51. package/src/2-proxy/pathValueProxy.ts +139 -0
  52. package/src/2-proxy/schema2.ts +518 -0
  53. package/src/3-path-functions/PathFunctionHelpers.ts +129 -0
  54. package/src/3-path-functions/PathFunctionRunner.ts +619 -0
  55. package/src/3-path-functions/PathFunctionRunnerMain.ts +67 -0
  56. package/src/3-path-functions/deployBlock.ts +10 -0
  57. package/src/3-path-functions/deployCheck.ts +7 -0
  58. package/src/3-path-functions/deployMain.ts +160 -0
  59. package/src/3-path-functions/pathFunctionLoader.ts +282 -0
  60. package/src/3-path-functions/syncSchema.ts +475 -0
  61. package/src/3-path-functions/tests/functionsTest.ts +135 -0
  62. package/src/3-path-functions/tests/rejectTest.ts +77 -0
  63. package/src/4-dom/css.tsx +29 -0
  64. package/src/4-dom/cssTypes.d.ts +212 -0
  65. package/src/4-dom/qreact.tsx +2322 -0
  66. package/src/4-dom/qreactTest.tsx +417 -0
  67. package/src/4-querysub/Querysub.ts +877 -0
  68. package/src/4-querysub/QuerysubController.ts +620 -0
  69. package/src/4-querysub/copyEvent.ts +0 -0
  70. package/src/4-querysub/permissions.ts +289 -0
  71. package/src/4-querysub/permissionsShared.ts +1 -0
  72. package/src/4-querysub/querysubPrediction.ts +525 -0
  73. package/src/5-diagnostics/FullscreenModal.tsx +67 -0
  74. package/src/5-diagnostics/GenericFormat.tsx +165 -0
  75. package/src/5-diagnostics/Modal.tsx +79 -0
  76. package/src/5-diagnostics/Table.tsx +183 -0
  77. package/src/5-diagnostics/TimeGrouper.tsx +114 -0
  78. package/src/5-diagnostics/diskValueAudit.ts +216 -0
  79. package/src/5-diagnostics/memoryValueAudit.ts +442 -0
  80. package/src/5-diagnostics/nodeMetadata.ts +135 -0
  81. package/src/5-diagnostics/qreactDebug.tsx +309 -0
  82. package/src/5-diagnostics/shared.ts +26 -0
  83. package/src/5-diagnostics/synchronousLagTracking.ts +47 -0
  84. package/src/TestController.ts +35 -0
  85. package/src/allowclient.flag +0 -0
  86. package/src/bits.ts +86 -0
  87. package/src/buffers.ts +69 -0
  88. package/src/config.ts +53 -0
  89. package/src/config2.ts +48 -0
  90. package/src/diagnostics/ActionsHistory.ts +56 -0
  91. package/src/diagnostics/NodeViewer.tsx +503 -0
  92. package/src/diagnostics/SizeLimiter.ts +62 -0
  93. package/src/diagnostics/TimeDebug.tsx +18 -0
  94. package/src/diagnostics/benchmark.ts +139 -0
  95. package/src/diagnostics/errorLogs/ErrorLogController.ts +515 -0
  96. package/src/diagnostics/errorLogs/ErrorLogCore.ts +274 -0
  97. package/src/diagnostics/errorLogs/LogClassifiers.tsx +302 -0
  98. package/src/diagnostics/errorLogs/LogFilterUI.tsx +84 -0
  99. package/src/diagnostics/errorLogs/LogNotify.tsx +101 -0
  100. package/src/diagnostics/errorLogs/LogTimeSelector.tsx +724 -0
  101. package/src/diagnostics/errorLogs/LogViewer.tsx +757 -0
  102. package/src/diagnostics/errorLogs/hookErrors.ts +60 -0
  103. package/src/diagnostics/errorLogs/logFiltering.tsx +149 -0
  104. package/src/diagnostics/heapTag.ts +13 -0
  105. package/src/diagnostics/listenOnDebugger.ts +77 -0
  106. package/src/diagnostics/logs/DiskLoggerPage.tsx +572 -0
  107. package/src/diagnostics/logs/ObjectDisplay.tsx +165 -0
  108. package/src/diagnostics/logs/ansiFormat.ts +108 -0
  109. package/src/diagnostics/logs/diskLogGlobalContext.ts +38 -0
  110. package/src/diagnostics/logs/diskLogger.ts +305 -0
  111. package/src/diagnostics/logs/diskShimConsoleLogs.ts +32 -0
  112. package/src/diagnostics/logs/injectFileLocationToConsole.ts +50 -0
  113. package/src/diagnostics/logs/logGitHashes.ts +30 -0
  114. package/src/diagnostics/managementPages.tsx +289 -0
  115. package/src/diagnostics/periodic.ts +89 -0
  116. package/src/diagnostics/runSaturationTest.ts +416 -0
  117. package/src/diagnostics/satSchema.ts +64 -0
  118. package/src/diagnostics/trackResources.ts +82 -0
  119. package/src/diagnostics/watchdog.ts +55 -0
  120. package/src/errors.ts +132 -0
  121. package/src/forceProduction.ts +3 -0
  122. package/src/fs.ts +72 -0
  123. package/src/heapDumps.ts +666 -0
  124. package/src/https.ts +2 -0
  125. package/src/inject.ts +1 -0
  126. package/src/library-components/ATag.tsx +84 -0
  127. package/src/library-components/Button.tsx +344 -0
  128. package/src/library-components/ButtonSelector.tsx +64 -0
  129. package/src/library-components/DropdownCustom.tsx +151 -0
  130. package/src/library-components/DropdownSelector.tsx +32 -0
  131. package/src/library-components/Input.tsx +334 -0
  132. package/src/library-components/InputLabel.tsx +198 -0
  133. package/src/library-components/InputPicker.tsx +125 -0
  134. package/src/library-components/LazyComponent.tsx +62 -0
  135. package/src/library-components/MeasureHeightCSS.tsx +48 -0
  136. package/src/library-components/MeasuredDiv.tsx +47 -0
  137. package/src/library-components/ShowMore.tsx +51 -0
  138. package/src/library-components/SyncedController.ts +171 -0
  139. package/src/library-components/TimeRangeSelector.tsx +407 -0
  140. package/src/library-components/URLParam.ts +263 -0
  141. package/src/library-components/colors.tsx +14 -0
  142. package/src/library-components/drag.ts +114 -0
  143. package/src/library-components/icons.tsx +692 -0
  144. package/src/library-components/niceStringify.ts +50 -0
  145. package/src/library-components/renderToString.ts +52 -0
  146. package/src/misc/PromiseRace.ts +101 -0
  147. package/src/misc/color.ts +30 -0
  148. package/src/misc/getParentProcessId.cs +53 -0
  149. package/src/misc/getParentProcessId.ts +53 -0
  150. package/src/misc/hash.ts +83 -0
  151. package/src/misc/ipPong.js +13 -0
  152. package/src/misc/networking.ts +2 -0
  153. package/src/misc/random.ts +45 -0
  154. package/src/misc.ts +19 -0
  155. package/src/noserverhotreload.flag +0 -0
  156. package/src/path.ts +226 -0
  157. package/src/persistentLocalStore.ts +37 -0
  158. package/src/promise.ts +15 -0
  159. package/src/server.ts +73 -0
  160. package/src/src.d.ts +1 -0
  161. package/src/test/heapProcess.ts +36 -0
  162. package/src/test/mongoSatTest.tsx +55 -0
  163. package/src/test/satTest.ts +193 -0
  164. package/src/test/test.tsx +552 -0
  165. package/src/zip.ts +92 -0
  166. package/src/zipThreaded.ts +106 -0
  167. package/src/zipThreadedWorker.js +19 -0
  168. package/tsconfig.json +27 -0
  169. package/yarnSpec.txt +56 -0
@@ -0,0 +1,216 @@
1
+ import { delay, runInSerial, runInfinitePoll } from "socket-function/src/batching";
2
+ import { AuthorityPath, isInAuthority, nodePathAuthority, pathValueAuthority2 } from "../0-path-value-core/NodePathAuthorities";
3
+ import { ARCHIVE_FLUSH_LIMIT, MAX_ARCHIVE_PROPAGATION_DELAY, PathValue, authorityStorage, epochTime, pathValueArchives, pathWatcher } from "../0-path-value-core/pathValueCore";
4
+ import { isClient } from "../config2";
5
+ import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
6
+ import { PathValueArchives } from "../0-path-value-core/pathValueArchives";
7
+ import { formatNumber, formatTime } from "socket-function/src/formatting/format";
8
+ import { green } from "socket-function/src/formatting/logColors";
9
+ import debugbreak from "debugbreak";
10
+ import { isDevDebugbreak } from "../config";
11
+ import { DebugLog, getLogHistoryEquals } from "../0-path-value-core/debugLogs";
12
+ import { getOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
13
+ import { sort } from "socket-function/src/misc";
14
+
15
+ const AUDIT_INTERVAL = ARCHIVE_FLUSH_LIMIT;
16
+ const AUDIT_THRESHOLD = ARCHIVE_FLUSH_LIMIT;
17
+
18
+ export async function startDiskAuditLoop() {
19
+ if (isClient()) return;
20
+
21
+ runInfinitePoll(AUDIT_INTERVAL, diskAuditNow);
22
+ }
23
+
24
+ (globalThis as any).diskAuditNow = diskAuditNow;
25
+ export async function diskAuditNow() {
26
+ let selfAuthorities = nodePathAuthority.getSelfAuthorities();
27
+ for (let authority of selfAuthorities) {
28
+ await auditAuthority(authority);
29
+ }
30
+ }
31
+
32
+ const auditAuthority = runInSerial(async function auditAuthority(authority: AuthorityPath) {
33
+ let threshold = Date.now() - AUDIT_THRESHOLD;
34
+ let time = Date.now();
35
+ await checkAuthority(authority, threshold);
36
+
37
+ console.log(green(`Checked authority ${pathValueAuthority2.getArchiveDirectory(authority)} in ${formatTime(Date.now() - time)}`));
38
+ });
39
+
40
+ async function getAuthorityBuffers(authority: AuthorityPath, threshold: number) {
41
+ let bufferCache = new Map<string, Buffer>();
42
+ let loopCount = 0;
43
+ let reread = true;
44
+
45
+ while (reread) {
46
+ reread = false;
47
+ let valuePaths = await pathValueArchives.getValuePaths(authority);
48
+ valuePaths = valuePaths.filter(path => {
49
+ let obj = pathValueArchives.decodeDataPath(path);
50
+ if (obj.minTime && obj.minTime > threshold) return false;
51
+ return true;
52
+ });
53
+ for (let valuePath of valuePaths) {
54
+ if (bufferCache.has(valuePath)) continue;
55
+ let rawValue = (await pathValueArchives.getRawValueFiles([valuePath]))[0];
56
+ if (!rawValue) {
57
+ loopCount++;
58
+ if (loopCount > 10) {
59
+ console.error(`Looped too many times trying to get an consistent state. It looks as if pathValueArchives.getValuePaths(${pathValueAuthority2.getArchiveDirectory(authority)}) is returning an invalid/missing path, ${valuePath}`);
60
+ return undefined;
61
+ }
62
+ reread = true;
63
+ break;
64
+ }
65
+ bufferCache.set(valuePath, rawValue);
66
+ }
67
+ }
68
+ return Array.from(bufferCache.entries());
69
+ }
70
+
71
+ // NOTE: We only check for { path, Time }, because presumably the value won't become corrupted. HOWEVER,
72
+ // getting out of sync with the disk is not only possible, but likely, as there are probably hundreds
73
+ // places in which this can happen, due to complex race conditions.
74
+ async function checkAuthority(authority: AuthorityPath, threshold: number) {
75
+ let buffers = await getAuthorityBuffers(authority, threshold);
76
+ if (!buffers) return;
77
+
78
+ let diskValues = new Map<string, PathValue>();
79
+ for (let [path, data] of buffers) {
80
+ let values = await PathValueArchives.loadValuesFromBuffer({
81
+ path, data,
82
+ });
83
+ let countSinceDelay = 0;
84
+ for (let diskValue of values.values) {
85
+ countSinceDelay++;
86
+ if (countSinceDelay > 10_000) {
87
+ countSinceDelay = 0;
88
+ await delay("paintLoop");
89
+ }
90
+
91
+ let prev = diskValues.get(diskValue.path);
92
+ if (prev && prev.time.time > diskValue.time.time) continue;
93
+ diskValues.set(diskValue.path, diskValue);
94
+ }
95
+ }
96
+
97
+ let changedValues = new Set<PathValue>();
98
+ let removedValues = new Set<PathValue>();
99
+
100
+ let countSinceDelay = 0;
101
+ for (let [path, diskValue] of diskValues) {
102
+ if (countSinceDelay > 10_000) {
103
+ countSinceDelay = 0;
104
+ await delay("paintLoop");
105
+ }
106
+
107
+ if (!isInAuthority(authority, diskValue.path)) continue;
108
+ if (diskValue.time.time > threshold) continue;
109
+
110
+ // Ignore cases where deletes happened too recently, as they won't be reflected
111
+ // on the disk, so we should EXPECT a difference.
112
+ let lastChangedTime = authorityStorage.getLastChangedTime(diskValue.path);
113
+ if (lastChangedTime && lastChangedTime.time > threshold) continue;
114
+
115
+ let memoryValue = authorityStorage.getValueAtTime(diskValue.path);
116
+ let values = authorityStorage.getValuePlusHistory(diskValue.path);
117
+ let time = Date.now();
118
+ if (pathValueSerializer.compareValuePaths(memoryValue, diskValue)) {
119
+ // IF we hit this, it likely means our auditing is wrong, and we are
120
+ // creating spurious audit failures.
121
+ // OTHERWISE, it means we have a fundamental issue how we handle ValuePaths, because
122
+ // locally we aren't even running multiple servers, so we should NEVER get
123
+ // corrupted / out of sync data!
124
+ if (isDevDebugbreak()) {
125
+ let searchValue = path;
126
+ let logs: DebugLog[] = getLogHistoryEquals(path);
127
+ let atTime = Date.now();
128
+ console.error(`Audit mismatch for ${path} from detected at ${atTime}`);
129
+ console.log(`Disk value:`, pathValueSerializer.getPathValue(diskValue));
130
+ console.log(`Memory value:`, pathValueSerializer.getPathValue(memoryValue));
131
+ console.log(`Disk time:`, diskValue.time.time);
132
+ console.log(`Memory time:`, memoryValue?.time.time);
133
+ console.log();
134
+ sort(logs, x => x.time);
135
+ let now = Date.now();
136
+ for (let log of logs) {
137
+ console.log(`${log.type.padEnd(20, " ")}`, now - log.time, log.values, log.time);
138
+ }
139
+ debugbreak(2);
140
+ debugger;
141
+ console.log(path);
142
+ console.log(logs.length);
143
+ console.log(atTime);
144
+ }
145
+ changedValues.add(diskValue);
146
+ console.log(values, time, memoryValue, diskValue);
147
+ }
148
+ }
149
+
150
+ // OH! And then we also have to check for removed values! (Really the most important part,
151
+ // as this happens normally when we GC).
152
+ for (let [memoryPath, value] of authorityStorage.__auditValues()) {
153
+ if (countSinceDelay > 10_000) {
154
+ countSinceDelay = 0;
155
+ await delay("paintLoop");
156
+ }
157
+ if (value.length === 0) continue;
158
+ if (value[0].time.time > threshold) continue;
159
+ let latestValue = value[0];
160
+ if (!value[0].valid) {
161
+ let latestValidValue = value.find(v => v.valid);
162
+ if (!latestValidValue) continue;
163
+ latestValue = latestValidValue;
164
+ }
165
+ if (latestValue.time.time > threshold) continue;
166
+ // If the value is known to be GCable... we then it not existing is equivalent. ALSO,
167
+ // it should have really been removed from memory already? But that's the audit code's problem.
168
+ if (latestValue.canGCValue) continue;
169
+ let diskValue = diskValues.get(memoryPath);
170
+ // There is a value in memory, but not on disk, so we must remove it from memory.
171
+ if (!diskValue || diskValue.canGCValue) {
172
+ removedValues.add({
173
+ path: memoryPath,
174
+ time: epochTime,
175
+ value: undefined,
176
+ valid: true,
177
+ canGCValue: true,
178
+ isTransparent: true,
179
+ lockCount: 0,
180
+ locks: [],
181
+ });
182
+ }
183
+ }
184
+ if (changedValues.size > 0) {
185
+ let firstValue = changedValues.values().next().value as PathValue;
186
+ console.error(`Value mismatch between disk and memory for ${formatNumber(changedValues.size)} values. Ex: ${firstValue.path}`);
187
+ }
188
+ if (removedValues.size > 0) {
189
+ console.info(green(`Removing ${formatNumber(removedValues.size)} paths from memory which have been GCed on the disk.`));
190
+ }
191
+ if (changedValues.size > 0 || removedValues.size > 0) {
192
+ let allValues = new Set([...changedValues, ...removedValues]);
193
+ // Check again, as our lag waiting means there is a race condition between finding a removed
194
+ // path, and the user touching it again!
195
+ for (let valuePath of Array.from(allValues)) {
196
+ let lastChangedTime = authorityStorage.getLastChangedTime(valuePath.path);
197
+ if (lastChangedTime && lastChangedTime.time > threshold) {
198
+ allValues.delete(valuePath);
199
+ } else {
200
+ authorityStorage.resetForInitialTrigger(valuePath.path);
201
+ }
202
+ }
203
+ authorityStorage.ingestValues(Array.from(allValues), undefined, undefined);
204
+
205
+ if (allValues.size > 0) {
206
+ // NOTE: By triggering all of these as initial syncs... it SHOULD result in us clobbering the old
207
+ // values. AND, we shouldn't have to worry about race conditions, because this is synchronous,
208
+ // and so any new values will happen and be queued after us.
209
+ // - Except predictions might be clobbered... but that's fine, they don't really matter.
210
+ pathWatcher.triggerValuesChanged(allValues, undefined, {
211
+ parentPaths: new Set(),
212
+ values: allValues,
213
+ });
214
+ }
215
+ }
216
+ }
@@ -0,0 +1,442 @@
1
+ import { SocketFunction } from "socket-function/SocketFunction";
2
+ import { PathValueController } from "../0-path-value-core/PathValueController";
3
+ import { PathValue, authorityStorage, pathWatcher } from "../0-path-value-core/pathValueCore";
4
+ import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
5
+ import { measureFnc } from "socket-function/src/profiling/measure";
6
+ import { isNode, list, sort, timeInMinute, timeInSecond } from "socket-function/src/misc";
7
+ import { getSingleSizeEstimate } from "./shared";
8
+ import { devDebugbreak, isDevDebugbreak } from "../config";
9
+ import { nodePathAuthority } from "../0-path-value-core/NodePathAuthorities";
10
+ import { isClient } from "../config2";
11
+ import { StringSerialize } from "../-h-path-value-serialize/stringSerializer";
12
+ import { delay, runInfinitePoll } from "socket-function/src/batching";
13
+ import { remoteWatcher } from "../1-path-client/RemoteWatcher";
14
+ import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
15
+ import { getOwnNodeId, isOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
16
+ import debugbreak from "debugbreak";
17
+ import { DebugLog, DebugLogController, getLogHistoryEquals, getLogHistoryIncludes } from "../0-path-value-core/debugLogs";
18
+ import { getParentPathStr } from "../path";
19
+ import { diskAuditNow } from "./diskValueAudit";
20
+ import { yellow, green } from "socket-function/src/formatting/logColors";
21
+ import { formatNumber, formatPercent } from "socket-function/src/formatting/format";
22
+
23
+ const AUDIT_COUNT = 100;
24
+ const AUDIT_MAX_BYTES_PER_SECOND = isNode() ? 1024 * 1024 : 1024 * 10;
25
+ const AUDIT_INTERVAL = timeInMinute * 5;
26
+ const MAX_RESYNC_INTERVAL = timeInMinute * 60;
27
+ // NOTE: This is quite short, because it isn't really the network delay, but more our internal delay
28
+ // NOTE: Longer now, because I think we were running into lag issues
29
+ const MAX_ASSUMED_PROPAGATION_DELAY = 1000 * 30;
30
+ const MAX_AUDIT_GROUP_SIZE = 1000 * 10;
31
+
32
+ export async function startMemoryAuditLoop() {
33
+ // NOTE: For now... there are security issues with allowing clients to audit paths.
34
+ // We COULD update the algorithm to work with Querysub directly, but... this
35
+ // shouldn't be needed. Clients SHOULDN'T get out of sync (they are only talking
36
+ // with one service, Querysub), and if they do, the user can refresh to fix it.
37
+ // (Where as if a Querysub itself is out of sync, it need to fix it, otherwise
38
+ // every single client will get the wrong values).
39
+ if (isClient()) return;
40
+
41
+ // Re-enable this
42
+ runInfinitePoll(AUDIT_INTERVAL, async () => {
43
+ await auditSomeOfOurPaths();
44
+ await auditSomeRemotePaths();
45
+ });
46
+ }
47
+
48
+ async function fullMemoryAudit() {
49
+ console.log(yellow(`Starting full memory audit`));
50
+ {
51
+ let values = authorityStorage.__auditValues();
52
+ let paths = Array.from(values.keys());
53
+ await auditPaths({ paths });
54
+ }
55
+
56
+ let remoteNodeIds = remoteWatcher.getAllRemoteWatchedNodeIds();
57
+ for (let remoteNodeId of remoteNodeIds) {
58
+ console.log(yellow(` Auditing all paths from ${remoteNodeId}`));
59
+ let parentWatches = pathWatcher.getAllParentWatches();
60
+ // Get ALL paths under parent paths
61
+ // (hopefully this doesn't crash the server... it is dev triggered, so...
62
+ // they should know what they are doing)
63
+ let randomPaths = await ValueAuditController.nodes[remoteNodeId].getRandomPaths({
64
+ parentWatches: parentWatches,
65
+ count: Number.MAX_SAFE_INTEGER
66
+ });
67
+ await auditSpecificPaths({ paths: randomPaths, authorityNodeId: remoteNodeId });
68
+ }
69
+ console.log(green(`Finished full memory audit`));
70
+ }
71
+
72
+ async function auditSomeOfOurPaths() {
73
+ let ourPaths = await getRandomPaths({ count: AUDIT_COUNT });
74
+ await auditPaths({ paths: ourPaths });
75
+ }
76
+ // NOTE: Exposed for testing, but... might be useful in the future?
77
+ (globalThis as any).auditPaths = auditPaths;
78
+ async function auditPaths(config: {
79
+ paths: string[];
80
+ forceAudit?: boolean;
81
+ }) {
82
+ let { paths } = config;
83
+ for (let i = 0; i < paths.length; i += MAX_AUDIT_GROUP_SIZE) {
84
+ await auditPathsBase({
85
+ paths: paths.slice(i, i + MAX_AUDIT_GROUP_SIZE),
86
+ forceAudit: config.forceAudit,
87
+ });
88
+ await delay("paintLoop");
89
+ }
90
+ }
91
+ async function auditPathsBase(config: {
92
+ paths: string[];
93
+ forceAudit?: boolean;
94
+ }) {
95
+ const { paths } = config;
96
+ // Split paths by nodeId
97
+ let pathByAuthority = new Map<string, Set<string>>();
98
+
99
+ for (let path of paths) {
100
+ let remoteNodeId = remoteWatcher.getExistingWatchRemoteNodeId(path);
101
+ if (!remoteNodeId) continue;
102
+ if (isOwnNodeId(remoteNodeId)) continue;
103
+ let paths = pathByAuthority.get(remoteNodeId);
104
+ if (!paths) {
105
+ paths = new Set();
106
+ pathByAuthority.set(remoteNodeId, paths);
107
+ }
108
+ paths.add(path);
109
+ }
110
+ for (let [authorityNodeId, paths] of pathByAuthority) {
111
+ await auditSpecificPaths({
112
+ paths: Array.from(paths),
113
+ authorityNodeId,
114
+ forceAudit: config.forceAudit,
115
+ });
116
+ }
117
+ }
118
+ // Needed to find if we are missing any paths
119
+ async function auditSomeRemotePaths() {
120
+ let remoteNodeIds = remoteWatcher.getAllRemoteWatchedNodeIds();
121
+ if (remoteNodeIds.length === 0) return [];
122
+ // Pick a random one
123
+ let remoteNodeId = remoteNodeIds[Math.floor(Math.random() * remoteNodeIds.length)];
124
+ if (isOwnNodeId(remoteNodeId)) return;
125
+ // NOTE: The only remote paths we will be missing are from parent watches, so...
126
+ // only check random parent paths.
127
+ let parentWatches = pathWatcher.getAllParentWatches();
128
+ let randomParentWatches = sampleRandom({ count: AUDIT_COUNT, paths: parentWatches });
129
+ let randomPaths = await ValueAuditController.nodes[remoteNodeId].getRandomPaths({ parentWatches: randomParentWatches, count: AUDIT_COUNT });
130
+ let watched = pathWatcher.getWatchers(new Set(randomPaths.map(path => ({ path }))));
131
+ let watchedPaths = Array.from(new Set(Array.from(watched.values()).map(x => Array.from(x).map(x => x.path)).flat()));
132
+ if (watchedPaths.length > 0) {
133
+ await auditSpecificPaths({ paths: watchedPaths, authorityNodeId: remoteNodeId });
134
+ }
135
+ }
136
+
137
+ async function getRandomPaths(config: {
138
+ count: number;
139
+ }): Promise<string[]> {
140
+ let values = authorityStorage.__auditValues();
141
+ let paths = Object.keys(values);
142
+ let superset = sampleRandom({ count: config.count * 3, paths });
143
+ superset = superset.filter(path => !values.get(path)?.[0]?.event);
144
+ return superset.slice(0, config.count);
145
+ }
146
+ function sampleRandom(config: {
147
+ count: number;
148
+ paths: string[];
149
+ }): string[] {
150
+ let { count, paths } = config;
151
+ if (count > paths.length) count = paths.length;
152
+ if (count / paths.length < 0.3) {
153
+ // For few values looping is faster
154
+ return getLoopRandom();
155
+ } else {
156
+ // If all values have to be picking looping is too slow. Use sorting
157
+ return getSortedRandom();
158
+ }
159
+ function getLoopRandom() {
160
+ let pickedIndexes = new Set<number>();
161
+ while (pickedIndexes.size < count) {
162
+ let index = Math.floor(Math.random() * paths.length);
163
+ pickedIndexes.add(index);
164
+ }
165
+ return Array.from(pickedIndexes).map(i => paths[i]);
166
+ }
167
+ function getSortedRandom() {
168
+ let random = new Array(paths.length).map(() => Math.random());
169
+ let indexes = list(paths.length);
170
+ sort(indexes, i => random[i]);
171
+ return indexes.map(i => paths[i]);
172
+ }
173
+ }
174
+
175
+ const AUDIT_WINDOW_SIZE = 10;
176
+ let lastAuditsSizes: {
177
+ count: number;
178
+ size: number;
179
+ time: number;
180
+ }[] = [];
181
+
182
+ async function auditSpecificPaths(config: {
183
+ paths: string[];
184
+ authorityNodeId: string;
185
+ forceAudit?: boolean;
186
+ }) {
187
+ let { paths } = config;
188
+ for (let i = 0; i < paths.length; i += MAX_AUDIT_GROUP_SIZE) {
189
+ await auditSpecificPathsBase({
190
+ paths: paths.slice(i, i + MAX_AUDIT_GROUP_SIZE),
191
+ authorityNodeId: config.authorityNodeId,
192
+ forceAudit: config.forceAudit,
193
+ });
194
+ await delay("paintLoop");
195
+ if (i + MAX_AUDIT_GROUP_SIZE < paths.length) {
196
+ console.log(` Audit progress: ${formatPercent((i + MAX_AUDIT_GROUP_SIZE) / paths.length)} / ${formatNumber(paths.length)} paths`);
197
+ }
198
+ }
199
+ }
200
+ async function auditSpecificPathsBase(config: {
201
+ paths: string[];
202
+ authorityNodeId: string;
203
+ forceAudit?: boolean;
204
+ }) {
205
+ let { paths, authorityNodeId, forceAudit } = config;
206
+ paths = paths.filter(path => !nodePathAuthority.isSelfAuthority(path));
207
+ if (paths.length === 0) return;
208
+
209
+
210
+ // Limit paths based on recent audit sizes (if we are downloading too much data,
211
+ // audit fewer paths, up to just auditing a single path).
212
+ // Use getSingleSizeEstimate to get sizes
213
+ // If forceAudit, don't limit the paths
214
+ if (lastAuditsSizes.length > 0 && !config.forceAudit) {
215
+ let auditWindowTotalSize = lastAuditsSizes.map(v => v.size).reduce((a, b) => a + b, 0);
216
+ let auditWindowTime = Date.now() - lastAuditsSizes[0].time;
217
+ let auditWindowRate = auditWindowTotalSize / auditWindowTime;
218
+ let fractionToReduce = Math.min(1, auditWindowRate / AUDIT_MAX_BYTES_PER_SECOND);
219
+ let count = Math.max(1, Math.floor(paths.length * fractionToReduce));
220
+ paths = paths.slice(0, count);
221
+ }
222
+
223
+ let invalidValues = new Set<string>();
224
+ let remoteValues = await ValueAuditController.nodes[authorityNodeId].getPathValues({ paths });
225
+
226
+ {
227
+ let size = remoteValues.map(v =>
228
+ (v.value ? getSingleSizeEstimate(v.value) : 0)
229
+ + v.path.length * 2
230
+ ).reduce((a, b) => a + b, 0);
231
+ lastAuditsSizes.push({ count: paths.length, size, time: Date.now() });
232
+ while (lastAuditsSizes.length > AUDIT_WINDOW_SIZE) {
233
+ lastAuditsSizes.shift();
234
+ }
235
+ }
236
+
237
+ const THRESHOLD_TIME = Date.now() - MAX_ASSUMED_PROPAGATION_DELAY;
238
+ let waited = false;
239
+ for (let { path, value } of remoteValues) {
240
+ let ourValue = authorityStorage.getValueAtTime(path);
241
+ if (!ourValue && !value) continue;
242
+
243
+ // No point in auditing event values, they are definitely going to disappear shortly!
244
+ if (ourValue?.event || value?.event) continue;
245
+
246
+ // If the value JUST changed... assume we just haven't received the update yet.
247
+ // - This DOES cause issues if the value is changing VERY frequently, BUT...
248
+ // to know this we would have to
249
+ if (ourValue && ourValue.time.time > THRESHOLD_TIME) {
250
+ continue;
251
+ }
252
+
253
+ if (!pathValueSerializer.compareValuePaths(value, ourValue)) continue;
254
+
255
+ let ourTime = ourValue ? ourValue.time.time : 0;
256
+ let remoteTime = value ? value.time.time : 0;
257
+ let oursWasNew = ourTime > THRESHOLD_TIME;
258
+ let theirsWasNew = remoteTime > THRESHOLD_TIME;
259
+
260
+ // Wait, and try again
261
+ if (!waited) {
262
+ waited = true;
263
+ await delay(MAX_ASSUMED_PROPAGATION_DELAY);
264
+ }
265
+ ourValue = authorityStorage.getValueAtTime(path);
266
+ value = (await ValueAuditController.nodes[authorityNodeId].getPathValue(path)).value;
267
+
268
+ if (!pathValueSerializer.compareValuePaths(value, ourValue)) continue;
269
+ let THRESHOLD2 = Date.now() - MAX_ASSUMED_PROPAGATION_DELAY;
270
+ let ourTime2 = authorityStorage.getLastChangedTime(path)?.time || 0;
271
+ let oursIsNew = ourTime2 > THRESHOLD2;
272
+ let theirTime2 = value ? value.time.time : 0;
273
+ let theirsIsNew = theirTime2 > THRESHOLD2;
274
+ if ((theirsIsNew || theirsWasNew) && (oursIsNew || oursWasNew)) {
275
+ // Both are updating.Which means they will probably sync. It is highly unlikely for our
276
+ // value to be constantly updated, and stay wrong (and if it is,
277
+ // then resyncing won't help!)
278
+ continue;
279
+ }
280
+ let reason = remoteWatcher.getWatchReason(path);
281
+ if (!reason) continue;
282
+
283
+ if (isDevDebugbreak()) {
284
+ const ourNodeId = getOwnNodeId();
285
+ let logs: DebugLog[] = [];
286
+ let remoteLogs: DebugLog[] = [];
287
+ // NOTE: Actually, even if we included it because of a parent watch, we still only
288
+ // care about direct writes to this value.
289
+ if (reason === "parent") {
290
+ logs = getLogHistoryIncludes(path);
291
+ remoteLogs = await DebugLogController.nodes[authorityNodeId].getLogHistoryEquals(path);
292
+
293
+ let logs2 = getLogHistoryIncludes(getParentPathStr(path));
294
+ let remoteLogs2 = await DebugLogController.nodes[authorityNodeId].getLogHistoryEquals(getParentPathStr(path));
295
+ logs.push(...logs2);
296
+ remoteLogs.push(...remoteLogs2);
297
+ } else {
298
+ logs = getLogHistoryEquals(path);
299
+ remoteLogs = await DebugLogController.nodes[authorityNodeId].getLogHistoryEquals(path);
300
+ }
301
+ let atTime = Date.now();
302
+ console.error(`Audit mismatch for ${path} from ${authorityNodeId} detected at ${atTime}. Watching because: ${reason}`);
303
+ console.log(`Value Remote:`, pathValueSerializer.getPathValue(value));
304
+ console.log(`Value Local:`, pathValueSerializer.getPathValue(ourValue));
305
+ console.log(`Time Remote:`, value?.time.time);
306
+ console.log(`Time Local:`, ourValue?.time.time);
307
+ console.log(`Our node id:`, ourNodeId);
308
+ console.log(`Time now:`, atTime);
309
+ console.log();
310
+ let logObjs: { source: string, log: DebugLog }[] = [];
311
+ for (let log of logs) {
312
+ logObjs.push({ source: "local", log });
313
+ }
314
+ for (let log of remoteLogs) {
315
+ logObjs.push({ source: "remote", log });
316
+ }
317
+ sort(logObjs, x => x.log.time);
318
+ let now = Date.now();
319
+ for (let { source, log } of logObjs) {
320
+ console.log(`${source.padEnd(8, " ")} ${log.type.padEnd(20, " ")}`, now - log.time, log.values, log.time);
321
+ }
322
+ debugbreak(2);
323
+ debugger;
324
+ console.log(path);
325
+ console.log(value);
326
+ console.log(ourValue);
327
+ console.log(logs.length);
328
+ console.log(remoteLogs.length);
329
+ console.log(atTime);
330
+ }
331
+
332
+ invalidValues.add(path);
333
+ }
334
+ if (invalidValues.size > 0) {
335
+ await resyncInvalidValues({ paths: Array.from(invalidValues), authorityNodeId, fromForceAudit: forceAudit });
336
+ }
337
+ }
338
+
339
+ let lastResyncTime = 0;
340
+ // NOTE: For now, we'll just resync all watches, just to be safe.
341
+ async function resyncInvalidValues(config: {
342
+ paths: string[];
343
+ authorityNodeId: string;
344
+ fromForceAudit?: boolean;
345
+ }) {
346
+ const { paths, authorityNodeId } = config;
347
+
348
+ if (!config.fromForceAudit) {
349
+ let skipResync = Date.now() - lastResyncTime < MAX_RESYNC_INTERVAL;
350
+ // console.error, listing a few paths, authorityNodeId, etc
351
+ {
352
+ let samplePaths = paths.slice(0, 3);
353
+ console.error(`${skipResync ? "SKIPPING RESYNC DUE TO EXCESSIVE RESYNCS" : "Resyncing"} ${samplePaths.length} paths from ${authorityNodeId} due to audit mismatch. Some paths that failed are: ${JSON.stringify(samplePaths)}`);
354
+ }
355
+ if (skipResync) return;
356
+ }
357
+ lastResyncTime = Date.now();
358
+
359
+
360
+ // If we tell the remote server to audit from a forced audit... we might infinitely loop
361
+ // back and forth with audits, which defeats the point of auditing to increase stability!
362
+ if (isNode()) {
363
+ // Tell the remote server to audit the specific paths, securely.
364
+ // AND wait until it audits them.
365
+ await ValueAuditController.nodes[authorityNodeId].auditNow({ paths });
366
+ }
367
+
368
+ await remoteWatcher.refreshAllWatches(config.authorityNodeId);
369
+ }
370
+
371
+
372
+ class ValueAuditControllerBase {
373
+ public async getPathValue(path: string) {
374
+ let value = authorityStorage.getValueAtTime(path);
375
+ return { path, value };
376
+ }
377
+ public async getPathValues(config: {
378
+ paths: string[];
379
+ }): Promise<{
380
+ path: string;
381
+ value: PathValue | undefined;
382
+ }[]> {
383
+ return config.paths.map(path => {
384
+ let value = authorityStorage.getValueAtTime(path);
385
+ // Get the value, to mutate value, so it doesn't lazily store it,
386
+ // so the other size can get the actual value!
387
+ pathValueSerializer.getPathValue(value);
388
+ return { path, value };
389
+ });
390
+ }
391
+
392
+ public async getRandomPaths(config: {
393
+ count: number;
394
+ parentWatches: string[];
395
+ }): Promise<string[]> {
396
+ let paths: string[][] = [];
397
+ let pathCount = 0;
398
+ for (let parentWatch of config.parentWatches) {
399
+ let childPaths = Array.from(authorityStorage.getPathsFromParent(parentWatch) || []);
400
+ let sampled = sampleRandom({ count: config.count - paths.length, paths: childPaths });
401
+ paths.push(sampled);
402
+ pathCount += sampled.length;
403
+ if (pathCount >= config.count) {
404
+ break;
405
+ }
406
+ }
407
+ return paths.flat();
408
+ }
409
+
410
+ public async auditNow(config: {
411
+ paths: string[];
412
+ }) {
413
+ await auditPaths({ paths: config.paths, forceAudit: true });
414
+ }
415
+
416
+
417
+ public async diskAuditNow() {
418
+ await diskAuditNow();
419
+ }
420
+
421
+ public async memoryAuditNow() {
422
+ await fullMemoryAudit();
423
+ }
424
+ }
425
+
426
+ export const ValueAuditController = SocketFunction.register(
427
+ "ValueAuditController-216d498d-9480-4af2-b277-131584958b15",
428
+ new ValueAuditControllerBase(),
429
+ () => ({
430
+ getPathValue: {},
431
+ getPathValues: {},
432
+ getRandomPaths: {},
433
+ auditNow: {},
434
+
435
+ diskAuditNow: {},
436
+ memoryAuditNow: {},
437
+ }),
438
+ () => ({
439
+ hooks: [requiresNetworkTrustHook],
440
+ })
441
+ );
442
+