@agoric/swing-store 0.9.2-dev-7bfadd3.0 → 0.9.2-dev-f2445bc.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.
@@ -179,22 +179,31 @@ As a result, for each active vat, the first-stage Export Data contains a record
179
179
 
180
180
  The `openSwingStore()` function has an option named `keepTranscripts` (which defaults to `true`), which causes the transcriptStore to retain the old transcript items. A second option named `keepSnapshots` (which defaults to `false`) causes the snapStore to retain the old heap snapshots. Opening the swingStore with a `false` option does not necessarily delete the old items immediately, but they'll probably get deleted the next time the kernel triggers a heap snapshot or transcript-span rollover. Validators who care about minimizing their disk usage will want to set both to `false`. In the future, we will arrange the SwingStore SQLite tables to provide easy `sqlite3` CLI commands that will delete the old data, so validators can also periodically use the CLI command to prune it.
181
181
 
182
- When exporting, the `makeSwingStoreExporter()` function takes an `exportMode=` argument. This serves to limit the set of artifacts that will be provided in the export. The defined values of `exportMode` are:
183
- * `current`: include only the current transcript span and current snapshot for each vat: just the minimum set necessary for current operations
184
- * `archival`: include all available transcript spans
185
- * `debug`: include all available transcript spans *and* all available snapshots. The old snapshots are never necessary for normal operations, nor are they likely to be usefor for extreme upgrade scenarios, but they might be useful for some unusual debugging operation
182
+ When exporting, the `makeSwingStoreExporter()` function takes an `artifactMode` option (in an options bag). This serves to both limit, and provide some minimal guarantees about, the set of artifacts that will be provided in the export. The defined values of `artifactMode` each build upon the previous one:
186
183
 
187
- Note that `exportMode` does not affect the Export Data generated by the exporter (if we *ever* want to validate this optional data, the hashes are mandatory). It only affects the names returned by `getArtifactNames()`: the list will be smaller for `current` than for `archival`. Re-exporting from a pruned copy will lack the old data, even if the re-export uses `archival`, because the second SwingStore cannot magically reconstruct the missing data.
184
+ * `operational`: include only the current transcript span and current snapshot for each vat: just the minimum set necessary for current operations
185
+ * `replay`: add all transcript spans for the current incarnation
186
+ * `archival`: add all available transcript spans, even for old incarnations
187
+ * `debug`: add all available snapshots, giving you everything. The old snapshots are never necessary for normal operations, nor are they likely to be useful for extreme upgrade scenarios, but they might be useful for some unusual debugging operations or investigations
188
188
 
189
- Note that when a vat is terminated, we delete all information about it, including transcript items and snapshots, both current and old. This will remove all the Export Data records, and well as the matching artifacts from `getArtifactNames`.
189
+ For each mode, the export will fail if the data necessary for those artifacts is not available (e.g. it was previously pruned). For example, an export with `artifactMode: 'replay'` will fail unless every vat has all transcript entries for each one's current incarnation. The `archival` mode will fail to export unless every vat has *every* transcript entry, back to the very first incarnation.
190
190
 
191
- When importing, the `importSwingStore()` function takes an options bag, which has property named `includeHistorical`. This property defaults to `false`, which makes the importer ignore any historical artifacts present in the export dataset. To import the historical transcript spans (and snapshots), you must set it to `true`.
191
+ However the `debug` export mode will never fail: it merely dumps everything in the swingstore, without limits or completeness checks.
192
192
 
193
- So, to convey historical transcript spans from one swingstore to another, you must set three options along the way:
193
+ Note that `artifactMode` does not affect the Export Data generated by the exporter (because if we *ever* want to validate this optional data, the hashes are mandatory). It only affects the names returned by `getArtifactNames()`: `operational` returns a subset of `replay`, which returns a subset of `archival`. And re-exporting from a previously-pruned copy under `archival` mode will fail, because the second SwingStore cannot magically reconstruct the missing data.
194
194
 
195
- * the original swingstore must be opened with `{ includeHistorical: true }`, otherwise the old spans will be pruned immediately
196
- * the export must use `makeSwingStoreExporter(dirpath, 'archival')`, otherwise the export will omit the old spans
197
- * the import must use `importSwingStore(exporter, dirPath, { includeHistorical: true })`, otherwide teh import will ignore the old spans
195
+ Also note that when a vat is terminated, we delete all information about it, including transcript items and snapshots, both current and old. This will remove all the Export Data records, and well as the matching artifacts from `getArtifactNames`.
196
+
197
+ When importing, the `importSwingStore()` function's options bag takes a property named `artifactMode`, with the same meanings as for export. Importing with the `operational` mode will ignore any artifacts other than those needed for current operations, and will fail unless all such artifacts were available. Importing with `replay` will ignore spans from old incarnations, but will fail unless all spans from current incarnations are present. Importing with `archival` will fail unless all spans from all incarnations are present. There is no `debug` option during import.
198
+
199
+ `importSwingStore()` returns a swingstore, which means its options bag also contains the same options as `openSwingStore()`, including the `keepTranscripts` option. This defaults to `true`, but if it were overridden to `false`, then the new swingstore will delete transcript spans as soon as they are no longer needed for operational purposes (e.g. when `transcriptStore.rolloverSpan()` is called).
200
+
201
+ So, to avoid pruning current-incarnation historical transcript spans when exporting from one swingstore to another, you must set (or avoid overriding) the following options along the way:
202
+
203
+ * the original swingstore must not be opened with `{ keepTranscripts: false }`, otherwise the old spans will be pruned immediately
204
+ * the export must use `makeSwingStoreExporter(dirpath, { artifactMode: 'replay'})`, otherwise the export will omit the old spans
205
+ * the import must use `importSwingStore(exporter, dirPath, { artifactMode: 'replay'})`, otherwise the import will ignore the old spans
206
+ * the `importSwingStore` call (and all subsequent `openSwingStore` calls) must not use `keepTranscripts: false`, otherwise the new swingstore will prune historical spans as new ones are created (during `rolloverSpan`).
198
207
 
199
208
  ## Implementation Details
200
209
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/swing-store",
3
- "version": "0.9.2-dev-7bfadd3.0+7bfadd3",
3
+ "version": "0.9.2-dev-f2445bc.0+f2445bc",
4
4
  "description": "Persistent storage for SwingSet",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -21,8 +21,8 @@
21
21
  "lint:eslint": "eslint ."
22
22
  },
23
23
  "dependencies": {
24
- "@agoric/assert": "0.6.1-dev-7bfadd3.0+7bfadd3",
25
- "@agoric/internal": "0.3.3-dev-7bfadd3.0+7bfadd3",
24
+ "@agoric/assert": "0.6.1-dev-f2445bc.0+f2445bc",
25
+ "@agoric/internal": "0.3.3-dev-f2445bc.0+f2445bc",
26
26
  "@endo/base64": "^0.2.32",
27
27
  "@endo/bundle-source": "^2.5.2",
28
28
  "@endo/check-bundle": "^0.2.19",
@@ -45,5 +45,5 @@
45
45
  ],
46
46
  "timeout": "2m"
47
47
  },
48
- "gitHead": "7bfadd33dc68a625c2df2ad57da7a4c6ee52f7ea"
48
+ "gitHead": "f2445bcb471c26169ea8052d04e96d51ef9f1487"
49
49
  }
@@ -1,20 +1,19 @@
1
1
  /**
2
2
  * @param {import('./internal.js').SwingStoreInternal} internal
3
- * @param {'operational'} level
3
+ * @param {Omit<import('./internal.js').ArtifactMode, 'debug'>} checkMode
4
4
  * @returns {void}
5
5
  */
6
- export function assertComplete(internal, level) {
7
- assert.equal(level, 'operational'); // only option for now
6
+ export function assertComplete(internal, checkMode) {
8
7
  // every bundle must be populated
9
- internal.bundleStore.assertComplete(level);
8
+ internal.bundleStore.assertComplete(checkMode);
10
9
 
11
10
  // every 'isCurrent' transcript span must have all items
12
11
  // TODO: every vat with any data must have a isCurrent transcript
13
12
  // span
14
- internal.transcriptStore.assertComplete(level);
13
+ internal.transcriptStore.assertComplete(checkMode);
15
14
 
16
15
  // every 'inUse' snapshot must be populated
17
- internal.snapStore.assertComplete(level);
16
+ internal.snapStore.assertComplete(checkMode);
18
17
 
19
18
  // TODO: every isCurrent span that starts with load-snapshot has a
20
19
  // matching snapshot (counter-argument: swing-store should not know
@@ -16,6 +16,7 @@ import { createSHA256 } from './hasher.js';
16
16
  */
17
17
  /**
18
18
  * @typedef { import('./exporter').SwingStoreExporter } SwingStoreExporter
19
+ * @typedef { import('./internal.js').ArtifactMode } ArtifactMode
19
20
  *
20
21
  * @typedef {{
21
22
  * addBundle: (bundleID: string, bundle: Bundle) => void;
@@ -29,7 +30,7 @@ import { createSHA256 } from './hasher.js';
29
30
  * repairBundleRecord: (key: string, value: string) => void,
30
31
  * importBundleRecord: (key: string, value: string) => void,
31
32
  * importBundle: (name: string, dataProvider: () => Promise<Buffer>) => Promise<void>,
32
- * assertComplete: (level: 'operational') => void,
33
+ * assertComplete: (checkMode: Omit<ArtifactMode, 'debug'>) => void,
33
34
  * getExportRecords: () => IterableIterator<readonly [key: string, value: string]>,
34
35
  * getArtifactNames: () => AsyncIterableIterator<string>,
35
36
  * getBundleIDs: () => IterableIterator<string>,
@@ -162,8 +163,8 @@ export function makeBundleStore(db, ensureTxn, noteExport = () => {}) {
162
163
  return sqlGetPrunedBundles.all();
163
164
  }
164
165
 
165
- function assertComplete(level) {
166
- assert.equal(level, 'operational'); // for now
166
+ function assertComplete(checkMode) {
167
+ assert(checkMode !== 'debug', checkMode);
167
168
  const pruned = getPrunedBundles();
168
169
  if (pruned.length) {
169
170
  throw Fail`missing bundles for: ${pruned.join(',')}`;
package/src/exporter.js CHANGED
@@ -8,6 +8,8 @@ import { makeBundleStore } from './bundleStore.js';
8
8
  import { makeSnapStore } from './snapStore.js';
9
9
  import { makeSnapStoreIO } from './snapStoreIO.js';
10
10
  import { makeTranscriptStore } from './transcriptStore.js';
11
+ import { assertComplete } from './assertComplete.js';
12
+ import { validateArtifactMode } from './internal.js';
11
13
 
12
14
  /**
13
15
  * @template T
@@ -53,7 +55,7 @@ import { makeTranscriptStore } from './transcriptStore.js';
53
55
  *
54
56
  * Get a list of name of artifacts available from the swingStore. A name
55
57
  * returned by this method guarantees that a call to `getArtifact` on the same
56
- * exporter instance will succeed. The `exportMode` option to
58
+ * exporter instance will succeed. The `artifactMode` option to
57
59
  * `makeSwingStoreExporter` controls the filtering of the artifact names
58
60
  * yielded.
59
61
  *
@@ -75,22 +77,20 @@ import { makeTranscriptStore } from './transcriptStore.js';
75
77
  */
76
78
 
77
79
  /**
78
- * @typedef {'current' | 'archival' | 'debug'} ExportMode
80
+ * @typedef { object } ExportSwingStoreOptions
81
+ * @property { import('./internal.js').ArtifactMode } [artifactMode] What artifacts should/must the exporter provide?
79
82
  */
80
83
 
81
84
  /**
82
85
  * @param {string} dirPath
83
- * @param { ExportMode } exportMode
86
+ * @param { ExportSwingStoreOptions } [options]
84
87
  * @returns {SwingStoreExporter}
85
88
  */
86
- export function makeSwingStoreExporter(dirPath, exportMode = 'current') {
89
+ export function makeSwingStoreExporter(dirPath, options = {}) {
87
90
  typeof dirPath === 'string' || Fail`dirPath must be a string`;
88
- exportMode === 'current' ||
89
- exportMode === 'archival' ||
90
- exportMode === 'debug' ||
91
- Fail`invalid exportMode ${q(exportMode)}`;
92
- const exportHistoricalSnapshots = exportMode === 'debug';
93
- const exportHistoricalTranscripts = exportMode !== 'current';
91
+ const { artifactMode = 'operational' } = options;
92
+ validateArtifactMode(artifactMode);
93
+
94
94
  const filePath = dbFileInDirectory(dirPath);
95
95
  const db = sqlite3(filePath);
96
96
 
@@ -106,6 +106,12 @@ export function makeSwingStoreExporter(dirPath, exportMode = 'current') {
106
106
  const bundleStore = makeBundleStore(db, ensureTxn);
107
107
  const transcriptStore = makeTranscriptStore(db, ensureTxn, () => {});
108
108
 
109
+ if (artifactMode !== 'debug') {
110
+ // throw early if this DB will not be able to create all the desired artifacts
111
+ const internal = { snapStore, bundleStore, transcriptStore };
112
+ assertComplete(internal, artifactMode);
113
+ }
114
+
109
115
  const sqlGetAllKVData = db.prepare(`
110
116
  SELECT key, value
111
117
  FROM kvStore
@@ -132,8 +138,8 @@ export function makeSwingStoreExporter(dirPath, exportMode = 'current') {
132
138
  * @yields {string}
133
139
  */
134
140
  async function* getArtifactNames() {
135
- yield* snapStore.getArtifactNames(exportHistoricalSnapshots);
136
- yield* transcriptStore.getArtifactNames(exportHistoricalTranscripts);
141
+ yield* snapStore.getArtifactNames(artifactMode);
142
+ yield* transcriptStore.getArtifactNames(artifactMode);
137
143
  yield* bundleStore.getArtifactNames();
138
144
  }
139
145
 
package/src/importer.js CHANGED
@@ -2,11 +2,12 @@ import { Fail, q } from '@agoric/assert';
2
2
 
3
3
  import { makeSwingStore } from './swingStore.js';
4
4
  import { buffer } from './util.js';
5
+ import { validateArtifactMode } from './internal.js';
5
6
  import { assertComplete } from './assertComplete.js';
6
7
 
7
8
  /**
8
9
  * @typedef { object } ImportSwingStoreOptions
9
- * @property { boolean } [includeHistorical] Should the importer pay attention to historical artifacts?
10
+ * @property { import('./internal.js').ArtifactMode } [artifactMode] What artifacts should the importer use and require?
10
11
  */
11
12
 
12
13
  /**
@@ -16,15 +17,17 @@ import { assertComplete } from './assertComplete.js';
16
17
  *
17
18
  * @param {import('./exporter').SwingStoreExporter} exporter
18
19
  * @param {string | null} [dirPath]
19
- * @param {ImportSwingStoreOptions} options
20
+ * @param {ImportSwingStoreOptions} [options]
20
21
  * @returns {Promise<import('./swingStore').SwingStore>}
21
22
  */
22
23
  export async function importSwingStore(exporter, dirPath = null, options = {}) {
23
24
  if (dirPath && typeof dirPath !== 'string') {
24
25
  Fail`dirPath must be a string`;
25
26
  }
26
- const { includeHistorical = false } = options;
27
- const store = makeSwingStore(dirPath, true, options);
27
+ const { artifactMode = 'operational', ...makeSwingStoreOptions } = options;
28
+ validateArtifactMode(artifactMode);
29
+
30
+ const store = makeSwingStore(dirPath, true, makeSwingStoreOptions);
28
31
  const { kernelStorage, internal } = store;
29
32
 
30
33
  // For every exportData entry, we add a DB record. 'kv' entries are
@@ -81,11 +84,10 @@ export async function importSwingStore(exporter, dirPath = null, options = {}) {
81
84
 
82
85
  // All the metadata is now installed, and we're prepared for
83
86
  // artifacts. We walk `getArtifactNames()` and offer each one to the
84
- // submodule, which ignores historical ones (unless
85
- // 'includeHistorical' is true), and validates+accepts the
86
- // rest. This is an initial import, so we don't need to check if we
87
- // already have the data, but the submodule function is free to do
88
- // that check if they want.
87
+ // submodule, which may ignore it according to `artifactMode`, but
88
+ // otherwise validates and accepts it. This is an initial import, so
89
+ // we don't need to check if we already have the data, but the
90
+ // submodule function is free to do such checks.
89
91
 
90
92
  for await (const name of exporter.getArtifactNames()) {
91
93
  const makeChunkIterator = () => exporter.getArtifact(name);
@@ -98,13 +100,13 @@ export async function importSwingStore(exporter, dirPath = null, options = {}) {
98
100
  await internal.bundleStore.importBundle(name, dataProvider);
99
101
  } else if (tag === 'snapshot') {
100
102
  await internal.snapStore.populateSnapshot(name, makeChunkIterator, {
101
- includeHistorical,
103
+ artifactMode,
102
104
  });
103
105
  } else if (tag === 'transcript') {
104
106
  await internal.transcriptStore.populateTranscriptSpan(
105
107
  name,
106
108
  makeChunkIterator,
107
- { includeHistorical },
109
+ { artifactMode },
108
110
  );
109
111
  } else {
110
112
  Fail`unknown artifact type ${q(tag)} on import`;
@@ -112,9 +114,11 @@ export async function importSwingStore(exporter, dirPath = null, options = {}) {
112
114
  }
113
115
 
114
116
  // We've installed all the artifacts that we could, now do a
115
- // completeness check.
117
+ // completeness check. Enforce at least 'operational' completeness,
118
+ // even if the given mode was 'debug'.
116
119
 
117
- assertComplete(internal, 'operational');
120
+ const checkMode = artifactMode === 'debug' ? 'operational' : artifactMode;
121
+ assertComplete(internal, checkMode);
118
122
 
119
123
  await exporter.close();
120
124
  return store;
package/src/internal.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { Fail, q } from '@agoric/assert';
2
+
1
3
  /**
2
4
  * @typedef { import('./snapStore').SnapStoreInternal } SnapStoreInternal
3
5
  * @typedef { import('./transcriptStore').TranscriptStoreInternal } TranscriptStoreInternal
@@ -8,7 +10,13 @@
8
10
  * snapStore: SnapStoreInternal,
9
11
  * bundleStore: BundleStoreInternal,
10
12
  * }} SwingStoreInternal
13
+ *
14
+ * @typedef {'operational' | 'replay' | 'archival' | 'debug'} ArtifactMode
11
15
  */
12
16
 
13
- // Ensure this is a module.
14
- export {};
17
+ export const artifactModes = ['operational', 'replay', 'archival', 'debug'];
18
+ export function validateArtifactMode(artifactMode) {
19
+ if (!artifactModes.includes(artifactMode)) {
20
+ Fail`invalid artifactMode ${q(artifactMode)}`;
21
+ }
22
+ }
@@ -60,6 +60,8 @@ export async function doRepairMetadata(internal, exporter) {
60
60
  }
61
61
 
62
62
  // and do a completeness check
63
- assertComplete(internal, 'operational');
63
+ /** @type { import('./internal.js').ArtifactMode } */
64
+ const artifactMode = 'operational';
65
+ assertComplete(internal, artifactMode);
64
66
  await exporter.close();
65
67
  }
package/src/snapStore.js CHANGED
@@ -31,6 +31,7 @@ import { buffer } from './util.js';
31
31
 
32
32
  /**
33
33
  * @typedef { import('./exporter').SwingStoreExporter } SwingStoreExporter
34
+ * @typedef { import('./internal.js').ArtifactMode } ArtifactMode
34
35
  *
35
36
  * @typedef {{
36
37
  * loadSnapshot: (vatID: string) => AsyncIterableIterator<Uint8Array>,
@@ -44,10 +45,10 @@ import { buffer } from './util.js';
44
45
  * @typedef {{
45
46
  * exportSnapshot: (name: string) => AsyncIterableIterator<Uint8Array>,
46
47
  * getExportRecords: (includeHistorical: boolean) => IterableIterator<readonly [key: string, value: string]>,
47
- * getArtifactNames: (includeHistorical: boolean) => AsyncIterableIterator<string>,
48
+ * getArtifactNames: (artifactMode: ArtifactMode) => AsyncIterableIterator<string>,
48
49
  * importSnapshotRecord: (key: string, value: string) => void,
49
- * populateSnapshot: (name: string, makeChunkIterator: () => AnyIterableIterator<Uint8Array>, options: { includeHistorical: boolean }) => Promise<void>,
50
- * assertComplete: (level: 'operational') => void,
50
+ * populateSnapshot: (name: string, makeChunkIterator: () => AnyIterableIterator<Uint8Array>, options: { artifactMode: ArtifactMode }) => Promise<void>,
51
+ * assertComplete: (checkMode: Omit<ArtifactMode, 'debug'>) => void,
51
52
  * repairSnapshotRecord: (key: string, value: string) => void,
52
53
  * }} SnapStoreInternal
53
54
  *
@@ -481,11 +482,11 @@ export function makeSnapStore(
481
482
  }
482
483
  }
483
484
 
484
- async function* getArtifactNames(includeHistorical) {
485
+ async function* getArtifactNames(artifactMode) {
485
486
  for (const rec of sqlGetAvailableSnapshots.iterate(1)) {
486
487
  yield snapshotArtifactName(rec);
487
488
  }
488
- if (includeHistorical) {
489
+ if (artifactMode === 'debug') {
489
490
  for (const rec of sqlGetAvailableSnapshots.iterate(null)) {
490
491
  yield snapshotArtifactName(rec);
491
492
  }
@@ -564,12 +565,12 @@ export function makeSnapStore(
564
565
  * @param {string} name Artifact name of the snapshot
565
566
  * @param {() => AnyIterableIterator<Uint8Array>} makeChunkIterator get an iterator of snapshot byte chunks
566
567
  * @param {object} options
567
- * @param {boolean} options.includeHistorical
568
+ * @param {ArtifactMode} options.artifactMode
568
569
  * @returns {Promise<void>}
569
570
  */
570
571
  async function populateSnapshot(name, makeChunkIterator, options) {
571
572
  ensureTxn();
572
- const { includeHistorical } = options;
573
+ const { artifactMode } = options;
573
574
  const parts = name.split('.');
574
575
  const [type, vatID, rawEndPos] = parts;
575
576
  // prettier-ignore
@@ -580,7 +581,7 @@ export function makeSnapStore(
580
581
  sqlGetSnapshotHashFor.get(vatID, snapPos) ||
581
582
  Fail`no metadata for snapshot ${name}`;
582
583
 
583
- if (!metadata.inUse && !includeHistorical) {
584
+ if (!metadata.inUse && artifactMode !== 'debug') {
584
585
  return; // ignore old snapshots
585
586
  }
586
587
 
@@ -617,8 +618,8 @@ export function makeSnapStore(
617
618
  `);
618
619
  sqlListPrunedCurrentSnapshots.pluck();
619
620
 
620
- function assertComplete(level) {
621
- assert.equal(level, 'operational'); // for now
621
+ function assertComplete(checkMode) {
622
+ assert(checkMode !== 'debug', checkMode);
622
623
  // every 'inUse' snapshot must be populated
623
624
  const vatIDs = sqlListPrunedCurrentSnapshots.all();
624
625
  if (vatIDs.length) {