@agoric/cosmic-swingset 0.41.4-dev-26cd420.0 → 0.41.4-dev-76b2796.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 (2) hide show
  1. package/package.json +12 -12
  2. package/tools/inquisitor.mjs +84 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/cosmic-swingset",
3
- "version": "0.41.4-dev-26cd420.0+26cd420",
3
+ "version": "0.41.4-dev-76b2796.0+76b2796",
4
4
  "description": "Agoric's Cosmos blockchain integration",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,15 +22,15 @@
22
22
  "author": "Agoric",
23
23
  "license": "Apache-2.0",
24
24
  "dependencies": {
25
- "@agoric/builders": "0.1.1-dev-26cd420.0+26cd420",
26
- "@agoric/cosmos": "0.34.2-dev-26cd420.0+26cd420",
27
- "@agoric/deploy-script-support": "0.10.4-dev-26cd420.0+26cd420",
28
- "@agoric/internal": "0.3.3-dev-26cd420.0+26cd420",
29
- "@agoric/store": "0.9.3-dev-26cd420.0+26cd420",
30
- "@agoric/swing-store": "0.9.2-dev-26cd420.0+26cd420",
31
- "@agoric/swingset-vat": "0.32.3-dev-26cd420.0+26cd420",
32
- "@agoric/telemetry": "0.6.3-dev-26cd420.0+26cd420",
33
- "@agoric/vm-config": "0.1.1-dev-26cd420.0+26cd420",
25
+ "@agoric/builders": "0.1.1-dev-76b2796.0+76b2796",
26
+ "@agoric/cosmos": "0.34.2-dev-76b2796.0+76b2796",
27
+ "@agoric/deploy-script-support": "0.10.4-dev-76b2796.0+76b2796",
28
+ "@agoric/internal": "0.3.3-dev-76b2796.0+76b2796",
29
+ "@agoric/store": "0.9.3-dev-76b2796.0+76b2796",
30
+ "@agoric/swing-store": "0.9.2-dev-76b2796.0+76b2796",
31
+ "@agoric/swingset-vat": "0.32.3-dev-76b2796.0+76b2796",
32
+ "@agoric/telemetry": "0.6.3-dev-76b2796.0+76b2796",
33
+ "@agoric/vm-config": "0.1.1-dev-76b2796.0+76b2796",
34
34
  "@endo/bundle-source": "^4.0.0",
35
35
  "@endo/env-options": "^1.1.8",
36
36
  "@endo/errors": "^1.2.10",
@@ -52,7 +52,7 @@
52
52
  "tmp": "^0.2.1"
53
53
  },
54
54
  "devDependencies": {
55
- "@agoric/kmarshal": "0.1.1-dev-26cd420.0+26cd420",
55
+ "@agoric/kmarshal": "0.1.1-dev-76b2796.0+76b2796",
56
56
  "ava": "^5.3.0",
57
57
  "better-sqlite3": "^9.1.1",
58
58
  "c8": "^10.1.3"
@@ -83,5 +83,5 @@
83
83
  "typeCoverage": {
84
84
  "atLeast": 86.88
85
85
  },
86
- "gitHead": "26cd420bec244f459c724a5ca83316c2ecc0187e"
86
+ "gitHead": "76b2796c08ea5c797f7f3374a1d5d8616e11e6dd"
87
87
  }
@@ -89,7 +89,6 @@ const storeExportAPI = ['getExportRecords', 'getArtifactNames'];
89
89
  * @property {{name: string, critical: boolean}} options
90
90
  */
91
91
 
92
- // TODO: getVatAdminNode('v112') # scan the vatAdmin vom v2.vs.vom.* vrefs for value matching /\b${vatID}\b/
93
92
  /**
94
93
  * @param {object} opt
95
94
  * @param {Database} opt.db
@@ -102,6 +101,7 @@ export const makeHelpers = ({ db, EV }) => {
102
101
  const kvGet = key => sqlKVGet.get(key);
103
102
  const kvGetJSON = key => JSON.parse(/** @type {string} */ (kvGet(key)));
104
103
 
104
+ /** @typedef {{key: string, value: string}} KVEntry */
105
105
  const sqlKVByRange = db.prepare(
106
106
  `SELECT key, value FROM kvStore WHERE key >= :a AND key < :b AND ${[
107
107
  '(:keySuffix IS NULL OR substr(key, -length(:keySuffix)) = :keySuffix)',
@@ -116,9 +116,26 @@ export const makeHelpers = ({ db, EV }) => {
116
116
  '(:valueGlob IS NULL OR value GLOB :valueGlob)',
117
117
  ].join(' AND ')}`,
118
118
  );
119
- const kvGlob = (keyGlob, valueGlob = undefined, lazy = false) => {
119
+ /**
120
+ * @template {boolean} Lazy
121
+ * @param {string} keyGlob
122
+ * @param {string} [valueGlob]
123
+ * @param {Lazy} [lazy]
124
+ * @returns {Lazy extends true ? Iterable<KVEntry> : Array<KVEntry>}
125
+ */
126
+ const kvGlob = (
127
+ keyGlob,
128
+ valueGlob = undefined,
129
+ lazy = /** @type {Lazy} */ (false),
130
+ ) => {
131
+ // Glob metasyntax is `*` (any substring), `?` (any one character), and
132
+ // `[$chars]`/`[]$chars]` (any one character from inside the brackets, with
133
+ // a requirement that `]` for matching is only valid immediately after the
134
+ // opening bracket).
120
135
  const [_keyPattern, keyPrefix, keyTail, keySuffix] =
121
- /** @type {string[]} */ (/^([^*?]*)((?:[*?]([^*?]*))*)$/.exec(keyGlob));
136
+ /** @type {string[]} */ (
137
+ /^([^*?[]*)((?:(?:[*?]|\[\]?[^\]]*\])([^*?[]*))*)$/.exec(keyGlob)
138
+ );
122
139
  let sql = sqlKVByHalfRange;
123
140
  /** @type {Record<'a' | 'b' | 'keySuffix' | 'keyGlob' | 'valueGlob', string | null>} */
124
141
  const args = {
@@ -137,7 +154,59 @@ export const makeHelpers = ({ db, EV }) => {
137
154
  } else {
138
155
  console.warn('Warning: Unprefixed searches can be slow');
139
156
  }
140
- return lazy ? sql.iterate(args) : sql.all(args);
157
+ return /** @type {any} */ (lazy ? sql.iterate(args) : sql.all(args));
158
+ };
159
+
160
+ // We need a RegExp.escape polyfill at least until our minimum supported
161
+ // Node.js includes it natively, however this is harmless to keep even then.
162
+ // It leaves leading ASCII alphanumerics untouched for readability and uses
163
+ // `\u00##` rather than `\x##` for simplicity, neither of which impact
164
+ // `scanVatStore`.
165
+ const needsEscape =
166
+ /([\^$\\.*+?()[\]{}|/])|[-,=<>#&!%:;@~''``""\s\uD800-\uDFFF]/gu;
167
+ const controlEscapes = {
168
+ '\t': '\\t',
169
+ '\n': '\\n',
170
+ '\v': '\\v',
171
+ '\f': '\\f',
172
+ '\r': '\\r',
173
+ };
174
+ const escapeChar = (ch, simple) => {
175
+ if (simple) return `\\${simple}`;
176
+ if (Object.hasOwn(controlEscapes, ch)) return controlEscapes[ch];
177
+ return ch
178
+ .split('')
179
+ .map(cu => `\\u${cu.charCodeAt(0).toString(16).padStart(4, '0')}`)
180
+ .join('');
181
+ };
182
+ const RegExpEscape = str => str.replace(needsEscape, escapeChar);
183
+
184
+ /**
185
+ * Return entries of a vat's KV store that mention `ref` as a distinct word.
186
+ * `ref` may be a vref such as "o-123", but may also be a value such as "123"
187
+ * or "v123" (that last case being particularly useful for finding a vat admin
188
+ * node in vatAdmin's KV store).
189
+ *
190
+ * @param {string} vatID
191
+ * @param {string} ref
192
+ */
193
+ const scanVatStore = (vatID, ref) => {
194
+ // Use a glob match to find candidates, then a regular expression to select
195
+ // only those with word boundaries around the ref (e.g., we don't want to
196
+ // consider "o-1234" a match for "o-123").
197
+ const candidates = kvGlob(
198
+ `${vatID}.vs.*`,
199
+ `*${ref.replace(/[*?[]/g, '[$&]')}*`,
200
+ true,
201
+ );
202
+ // The non-capturing group serves no functional purpose; it just improves
203
+ // readability in the event that a debugging session finds itself here.
204
+ const re = RegExp(String.raw`\b(?:${RegExpEscape(ref)})\b`);
205
+ const matches = [];
206
+ for (const entry of candidates) {
207
+ if (entry.value.match(re)) matches.push(entry);
208
+ }
209
+ return matches;
141
210
  };
142
211
 
143
212
  /**
@@ -315,7 +384,16 @@ export const makeHelpers = ({ db, EV }) => {
315
384
 
316
385
  return harden({
317
386
  runCoreEval,
318
- stable: { db, getRefs, kvGet, kvGetJSON, kvGlob, vatsByID, vatsByName },
387
+ stable: {
388
+ db,
389
+ getRefs,
390
+ kvGet,
391
+ kvGetJSON,
392
+ kvGlob,
393
+ scanVatStore,
394
+ vatsByID,
395
+ vatsByName,
396
+ },
319
397
  });
320
398
  };
321
399
  /** @typedef {ReturnType<typeof makeHelpers>} Helpers */
@@ -820,7 +898,7 @@ May be used interactively, or as a recipient of piped commands, or as a module.
820
898
  Example commands:
821
899
  * stable.db.prepare("SELECT name FROM sqlite_schema WHERE type='table'").pluck().all();
822
900
  * stable.db.pragma("table_info(transcriptSpans)");
823
- * [vatAdminNodeRow] = stable.db.kvGlob('v2.vs.*', '*v100*');
901
+ * [vatAdminNodeEntry] = stable.scanVatStore('v2', 'v100');
824
902
  * stable.getRefs('o+10', 'v1');
825
903
  * board = await EV.vat('bootstrap').consumeItem('board');
826
904
  * obj = await EV(board).getValue('board02963');