@agoric/swing-store 0.9.2-u13.0 → 0.9.2-u16.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.
- package/CHANGELOG.md +15 -36
- package/docs/data-export.md +79 -52
- package/package.json +21 -15
- package/src/bundleStore.js +5 -2
- package/src/exporter.js +20 -13
- package/src/importer.js +2 -2
- package/src/internal.js +3 -3
- package/src/repairMetadata.js +1 -1
- package/src/snapStore.js +12 -5
- package/src/snapStoreIO.js +1 -1
- package/src/swingStore.js +16 -11
- package/src/transcriptStore.js +6 -0
- package/src/util.js +1 -1
- package/test/{test-bundles.js → bundles.test.js} +1 -2
- package/test/{test-deletion.js → deletion.test.js} +1 -1
- package/test/{test-export.js → export.test.js} +1 -3
- package/test/{test-exportImport.js → exportImport.test.js} +8 -12
- package/test/exports.js +102 -0
- package/test/{test-hasher.js → hasher.test.js} +0 -2
- package/test/{test-import.js → import.test.js} +11 -108
- package/test/{test-repair-metadata.js → repair-metadata.test.js} +1 -3
- package/test/{test-snapstore.js → snapstore.test.js} +2 -5
- package/test/{test-state.js → state.test.js} +2 -3
- package/test/util.js +1 -0
- package/{jsconfig.json → tsconfig.json} +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,50 +3,29 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
### [0.9.2-
|
|
6
|
+
### [0.9.2-u16.0](https://github.com/Agoric/agoric-sdk/compare/@agoric/swing-store@0.9.1...@agoric/swing-store@0.9.2-u16.0) (2024-07-02)
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
### Features
|
|
10
10
|
|
|
11
|
-
* add exporter.getHostKV() API ([
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
### Features
|
|
19
|
-
|
|
20
|
-
* **swing-store:** faster import of swing-store ([35aef87](https://github.com/Agoric/agoric-sdk/commit/35aef87ec0f10b7f0cdce462ac0509296e8bd752))
|
|
21
|
-
* **swing-store:** prevent SwingSet usage of imported swing-store ([03f642d](https://github.com/Agoric/agoric-sdk/commit/03f642d39f90ef9465a439723c3a69beef73bd61))
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
### Bug Fixes
|
|
25
|
-
|
|
26
|
-
* **swing-store:** ensure crank savepoint is wrapped in transaction ([8d738c6](https://github.com/Agoric/agoric-sdk/commit/8d738c65ed37b9159e94fbcf291ed7fe8478ee5a))
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
### [0.9.2-u11wf.0](https://github.com/Agoric/agoric-sdk/compare/@agoric/swing-store@0.9.2-u11.0...@agoric/swing-store@0.9.2-u11wf.0) (2023-09-23)
|
|
31
|
-
|
|
32
|
-
**Note:** Version bump only for package @agoric/swing-store
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
### [0.9.2-u11.0](https://github.com/Agoric/agoric-sdk/compare/@agoric/swing-store@0.9.1...@agoric/swing-store@0.9.2-u11.0) (2023-08-24)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
### Features
|
|
42
|
-
|
|
43
|
-
* **swingstore:** add repairMetadata() ([5b2d19d](https://github.com/Agoric/agoric-sdk/commit/5b2d19d1153a23c118afb14ca4ed80e175640f62)), closes [#8025](https://github.com/Agoric/agoric-sdk/issues/8025) [#8025](https://github.com/Agoric/agoric-sdk/issues/8025)
|
|
11
|
+
* add exporter.getHostKV() API ([eb564f9](https://github.com/Agoric/agoric-sdk/commit/eb564f9635397c0706e1f8255b3e125681e2d031)), closes [#8523](https://github.com/Agoric/agoric-sdk/issues/8523)
|
|
12
|
+
* **swing-store:** faster import of swing-store ([0170568](https://github.com/Agoric/agoric-sdk/commit/0170568d66748af76f0bd24a4acdaa34b9c79cca))
|
|
13
|
+
* **swing-store:** prevent SwingSet usage of imported swing-store ([6a833eb](https://github.com/Agoric/agoric-sdk/commit/6a833ebda2b2ff0e72040ca8186f93ae91567add))
|
|
14
|
+
* **swingstore:** add repairMetadata() ([33b5c1c](https://github.com/Agoric/agoric-sdk/commit/33b5c1c1fefd5278a24cd5f06630b238439e2891)), closes [#8025](https://github.com/Agoric/agoric-sdk/issues/8025) [#8025](https://github.com/Agoric/agoric-sdk/issues/8025)
|
|
15
|
+
* tool for auditing dangling kindID references ([eeadc46](https://github.com/Agoric/agoric-sdk/commit/eeadc462d8fb09449e4ea6f0118ae8654e0c8e9b)), closes [#7655](https://github.com/Agoric/agoric-sdk/issues/7655)
|
|
44
16
|
|
|
45
17
|
|
|
46
18
|
### Bug Fixes
|
|
47
19
|
|
|
48
|
-
*
|
|
49
|
-
*
|
|
20
|
+
* misc runtime robustness found by typecheck ([a033f26](https://github.com/Agoric/agoric-sdk/commit/a033f2638f9f11e19d94d7931e4e0614773b1f60))
|
|
21
|
+
* performance.now() binding ([4a3b59b](https://github.com/Agoric/agoric-sdk/commit/4a3b59b486c0cb916e531d5de390fbf90e4421ad))
|
|
22
|
+
* rewrite importSwingStore to preserve metadata properly ([38c9efc](https://github.com/Agoric/agoric-sdk/commit/38c9efce10957e0eb245e25ae5f9f45792eb58ad)), closes [#8025](https://github.com/Agoric/agoric-sdk/issues/8025)
|
|
23
|
+
* **swing-store:** add 'replay' artifactMode, make export more strict ([9939ea6](https://github.com/Agoric/agoric-sdk/commit/9939ea699bb1fd0b711f950679b432eef9054fda)), closes [#8105](https://github.com/Agoric/agoric-sdk/issues/8105)
|
|
24
|
+
* **swing-store:** ensure crank savepoint is wrapped in transaction ([9d2dd3f](https://github.com/Agoric/agoric-sdk/commit/9d2dd3f9966940961a4c21351d256fa3615715d7))
|
|
25
|
+
* **swing-store:** explicitly harden prototypes ([86c128a](https://github.com/Agoric/agoric-sdk/commit/86c128a29b5ed61764a67644c3734b0c05df2993))
|
|
26
|
+
* **swing-store:** no completeness check when creating exporter ([d4df073](https://github.com/Agoric/agoric-sdk/commit/d4df073ffcc48b0f0e62bac107ee8edf21150ad9))
|
|
27
|
+
* **types:** template syntax ([279b903](https://github.com/Agoric/agoric-sdk/commit/279b903a559710511d69f1614badddeab801b90d))
|
|
28
|
+
* update for `[@jessie](https://github.com/jessie).js/safe-await-separator` ([94c6b3c](https://github.com/Agoric/agoric-sdk/commit/94c6b3c83a5326594f1e2886ae01d6a703a7a68f))
|
|
50
29
|
|
|
51
30
|
|
|
52
31
|
|
package/docs/data-export.md
CHANGED
|
@@ -39,30 +39,41 @@ The exporter is created by calling `makeSwingStoreExporter(dirpath)`, passing it
|
|
|
39
39
|
After calling `hostStorage.commit()`, the host application can extract the first-stage export data, and then the second-stage export artifacts:
|
|
40
40
|
|
|
41
41
|
```js
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
for (const [key, value] of exporter.getExportData()) {
|
|
56
|
-
exportData.set(key, value);
|
|
42
|
+
const dirPath = '.../swing-store';
|
|
43
|
+
const swingStore = openSwingStore(dirPath);
|
|
44
|
+
...
|
|
45
|
+
await controller.run();
|
|
46
|
+
hostStorage.commit();
|
|
47
|
+
// spawn a child process and wait for it to open a transaction
|
|
48
|
+
const started = makePromiseKit();
|
|
49
|
+
const child = fork(path, args);
|
|
50
|
+
child.on('error', started.reject);
|
|
51
|
+
child.on('exit', started.reject);
|
|
52
|
+
child.on('message', msg => {
|
|
53
|
+
if (msg?.type === 'started') {
|
|
54
|
+
started.resolve();
|
|
57
55
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
56
|
+
});
|
|
57
|
+
await started.promise;
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
// child process does:
|
|
61
|
+
import { buffer } from 'node:stream/consumers';
|
|
62
|
+
const exporter = makeSwingStoreExporter(dirPath);
|
|
63
|
+
// exporter now has a txn, so parent process is free to proceed forward
|
|
64
|
+
process.send({ type: 'started' });
|
|
65
|
+
const exportData = new Map();
|
|
66
|
+
for (const [key, value] of exporter.getExportData()) {
|
|
67
|
+
exportData.set(key, value);
|
|
68
|
+
}
|
|
69
|
+
const exportArtifacts = new Map();
|
|
70
|
+
for (const name of exporter.getArtifactNames()) {
|
|
71
|
+
const reader = exporter.getArtifact(name);
|
|
72
|
+
// reader is an async iterable of Uint8Array, e.g. a stream
|
|
73
|
+
const data = await buffer(reader);
|
|
74
|
+
exportArtifacts.set(name, data);
|
|
75
|
+
}
|
|
76
|
+
// export is the combination of 'exportData' and 'exportArtifacts'
|
|
66
77
|
```
|
|
67
78
|
|
|
68
79
|

|
|
@@ -92,35 +103,51 @@ Then, on the few occasions when the application needs to build a full state-sync
|
|
|
92
103
|

|
|
93
104
|
|
|
94
105
|
```js
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
106
|
+
const dirPath = '.../swing-store';
|
|
107
|
+
const iavl = ...;
|
|
108
|
+
function exportCallback(key, value) {
|
|
109
|
+
const iavlKey = `ssed.${key}`; // 'ssed' is short for SwingStoreExportData
|
|
110
|
+
if (value === undefined) {
|
|
111
|
+
iavl.delete(iavlKey);
|
|
112
|
+
} else {
|
|
113
|
+
iavl.set(iavlKey, value);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const swingStore = openSwingStore(dirPath, { exportCallback });
|
|
117
|
+
...
|
|
118
|
+
await controller.run();
|
|
119
|
+
hostStorage.commit();
|
|
120
|
+
|
|
121
|
+
// now, if the validator is configured to publish state-sync snapshots,
|
|
122
|
+
// and if this block height is one of the publishing points,
|
|
123
|
+
// do the following:
|
|
124
|
+
|
|
125
|
+
// spawn a child process and wait for it to open a transaction
|
|
126
|
+
const started = makePromiseKit();
|
|
127
|
+
const child = fork(path, args);
|
|
128
|
+
child.on('error', started.reject);
|
|
129
|
+
child.on('exit', started.reject);
|
|
130
|
+
child.on('message', msg => {
|
|
131
|
+
if (msg?.type === 'started') {
|
|
132
|
+
started.resolve();
|
|
122
133
|
}
|
|
123
|
-
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
await started.promise;
|
|
137
|
+
} catch (err) {
|
|
138
|
+
...
|
|
139
|
+
}
|
|
140
|
+
...
|
|
141
|
+
|
|
142
|
+
// child process does:
|
|
143
|
+
const exporter = makeSwingStoreExporter(dirPath);
|
|
144
|
+
process.send({ type: 'started' });
|
|
145
|
+
// note: no exporter.getExportData(), the first-stage data is already in IAVL
|
|
146
|
+
const artifacts = new Map();
|
|
147
|
+
for (const name of exporter.getArtifactNames()) {
|
|
148
|
+
artifacts.set(name, exporter.getArtifact(name));
|
|
149
|
+
}
|
|
150
|
+
// instruct cosmos-sdk to include 'artifacts' in the state-sync snapshot
|
|
124
151
|
```
|
|
125
152
|
|
|
126
153
|
## Import
|
|
@@ -202,7 +229,7 @@ SwingStore contains components to accommodate all the various kinds of state tha
|
|
|
202
229
|
* `kvStore`, a general-purpose string/string key-value table
|
|
203
230
|
* `transcriptStore`: append-only vat deliveries, broken into "spans", delimited by heap snapshot events
|
|
204
231
|
* `snapshotStore`: binary blobs containing JS engine heap state, to limit transcript replay depth
|
|
205
|
-
* `bundleStore`: code bundles that can be imported with
|
|
232
|
+
* `bundleStore`: code bundles that can be imported with [@endo/import-bundle](https://www.npmjs.com/package/@endo/import-bundle)
|
|
206
233
|
|
|
207
234
|
Currently, the SwingStore treats transcript spans, heap snapshots, and bundles as export artifacts, with hashes recorded in the export data for validation (and to remember exactly which artifacts are necessary). The `kvStore` is copied one-to-one into the export data (i.e. we keep a full shadow copy in IAVL), because that is the fastest way to ensure the `kvStore` data is fully available and validated.
|
|
208
235
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/swing-store",
|
|
3
|
-
"version": "0.9.2-
|
|
3
|
+
"version": "0.9.2-u16.0",
|
|
4
4
|
"description": "Persistent storage for SwingSet",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -17,23 +17,23 @@
|
|
|
17
17
|
"test:xs": "exit 0",
|
|
18
18
|
"lint-fix": "yarn lint:eslint --fix",
|
|
19
19
|
"lint": "run-s --continue-on-error lint:*",
|
|
20
|
-
"lint:types": "tsc
|
|
20
|
+
"lint:types": "tsc",
|
|
21
21
|
"lint:eslint": "eslint ."
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@agoric/assert": "^0.6.1-
|
|
25
|
-
"@agoric/internal": "^0.4.0-
|
|
26
|
-
"@endo/base64": "0.
|
|
27
|
-
"@endo/bundle-source": "2.
|
|
28
|
-
"@endo/check-bundle": "0.
|
|
29
|
-
"@endo/nat": "
|
|
30
|
-
"better-sqlite3": "^
|
|
24
|
+
"@agoric/assert": "^0.6.1-u16.0",
|
|
25
|
+
"@agoric/internal": "^0.4.0-u16.0",
|
|
26
|
+
"@endo/base64": "^1.0.5",
|
|
27
|
+
"@endo/bundle-source": "^3.2.3",
|
|
28
|
+
"@endo/check-bundle": "^1.0.7",
|
|
29
|
+
"@endo/nat": "^5.0.7",
|
|
30
|
+
"better-sqlite3": "^9.1.1"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@endo/init": "
|
|
34
|
-
"@types/better-sqlite3": "^7.
|
|
35
|
-
"ava": "^5.
|
|
36
|
-
"c8": "^
|
|
33
|
+
"@endo/init": "^1.1.2",
|
|
34
|
+
"@types/better-sqlite3": "^7.6.9",
|
|
35
|
+
"ava": "^5.3.0",
|
|
36
|
+
"c8": "^9.1.0",
|
|
37
37
|
"tmp": "^0.2.1"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
@@ -41,9 +41,15 @@
|
|
|
41
41
|
},
|
|
42
42
|
"ava": {
|
|
43
43
|
"files": [
|
|
44
|
-
"test
|
|
44
|
+
"test/**/*.test.*"
|
|
45
|
+
],
|
|
46
|
+
"require": [
|
|
47
|
+
"@endo/init/debug.js"
|
|
45
48
|
],
|
|
46
49
|
"timeout": "2m"
|
|
47
50
|
},
|
|
48
|
-
"
|
|
51
|
+
"typeCoverage": {
|
|
52
|
+
"atLeast": 76.31
|
|
53
|
+
},
|
|
54
|
+
"gitHead": "bbdf652c3f413381cb352a8a360db1063974fafd"
|
|
49
55
|
}
|
package/src/bundleStore.js
CHANGED
|
@@ -15,7 +15,7 @@ import { createSHA256 } from './hasher.js';
|
|
|
15
15
|
* @typedef { EndoZipBase64Bundle | GetExportBundle | NestedEvaluateBundle } Bundle
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
|
-
* @typedef { import('./exporter').SwingStoreExporter } SwingStoreExporter
|
|
18
|
+
* @typedef { import('./exporter.js').SwingStoreExporter } SwingStoreExporter
|
|
19
19
|
* @typedef { import('./internal.js').ArtifactMode } ArtifactMode
|
|
20
20
|
*
|
|
21
21
|
* @typedef {{
|
|
@@ -267,6 +267,7 @@ export function makeBundleStore(db, ensureTxn, noteExport = () => {}) {
|
|
|
267
267
|
const rawBundle = row.bundle || Fail`bundle ${q(bundleID)} pruned`;
|
|
268
268
|
yield* Readable.from(Buffer.from(rawBundle));
|
|
269
269
|
}
|
|
270
|
+
harden(exportBundle);
|
|
270
271
|
|
|
271
272
|
const sqlGetBundleIDs = db.prepare(`
|
|
272
273
|
SELECT bundleID
|
|
@@ -286,12 +287,14 @@ export function makeBundleStore(db, ensureTxn, noteExport = () => {}) {
|
|
|
286
287
|
yield [bundleArtifactName(bundleID), bundleID];
|
|
287
288
|
}
|
|
288
289
|
}
|
|
290
|
+
harden(getExportRecords);
|
|
289
291
|
|
|
290
292
|
async function* getArtifactNames() {
|
|
291
293
|
for (const bundleID of sqlGetBundleIDs.iterate()) {
|
|
292
294
|
yield bundleArtifactName(bundleID);
|
|
293
295
|
}
|
|
294
296
|
}
|
|
297
|
+
harden(getArtifactNames);
|
|
295
298
|
|
|
296
299
|
function computeSha512(bytes) {
|
|
297
300
|
const hash = createHash('sha512');
|
|
@@ -328,7 +331,6 @@ export function makeBundleStore(db, ensureTxn, noteExport = () => {}) {
|
|
|
328
331
|
endoZipBase64: encodeBase64(data),
|
|
329
332
|
});
|
|
330
333
|
// Assert that the bundle contents match the ID and hash
|
|
331
|
-
// eslint-disable-next-line @jessie.js/no-nested-await
|
|
332
334
|
await checkBundle(bundle, computeSha512, bundleID);
|
|
333
335
|
populateBundle(bundleID, serializeBundle(bundleID, bundle));
|
|
334
336
|
} else {
|
|
@@ -365,6 +367,7 @@ export function makeBundleStore(db, ensureTxn, noteExport = () => {}) {
|
|
|
365
367
|
function* getBundleIDs() {
|
|
366
368
|
yield* sqlListBundleIDs.iterate();
|
|
367
369
|
}
|
|
370
|
+
harden(getBundleIDs);
|
|
368
371
|
|
|
369
372
|
return harden({
|
|
370
373
|
importBundleRecord,
|
package/src/exporter.js
CHANGED
|
@@ -13,11 +13,11 @@ import { validateArtifactMode } from './internal.js';
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @template T
|
|
16
|
-
*
|
|
16
|
+
* @typedef { Iterable<T> | AsyncIterable<T> } AnyIterable
|
|
17
17
|
*/
|
|
18
18
|
/**
|
|
19
19
|
* @template T
|
|
20
|
-
*
|
|
20
|
+
* @typedef { IterableIterator<T> | AsyncIterableIterator<T> } AnyIterableIterator
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -111,12 +111,6 @@ export function makeSwingStoreExporter(dirPath, options = {}) {
|
|
|
111
111
|
const bundleStore = makeBundleStore(db, ensureTxn);
|
|
112
112
|
const transcriptStore = makeTranscriptStore(db, ensureTxn, () => {});
|
|
113
113
|
|
|
114
|
-
if (artifactMode !== 'debug') {
|
|
115
|
-
// throw early if this DB will not be able to create all the desired artifacts
|
|
116
|
-
const internal = { snapStore, bundleStore, transcriptStore };
|
|
117
|
-
assertComplete(internal, artifactMode);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
114
|
const sqlKVGet = db.prepare(`
|
|
121
115
|
SELECT value
|
|
122
116
|
FROM kvStore
|
|
@@ -141,9 +135,11 @@ export function makeSwingStoreExporter(dirPath, options = {}) {
|
|
|
141
135
|
function getHostKV(key) {
|
|
142
136
|
typeof key === 'string' || Fail`key must be a string`;
|
|
143
137
|
getKeyType(key) === 'host' || Fail`getHostKV requires host keys`;
|
|
138
|
+
// @ts-expect-error unknown
|
|
144
139
|
return sqlKVGet.get(key);
|
|
145
140
|
}
|
|
146
141
|
|
|
142
|
+
/** @type {any} */
|
|
147
143
|
const sqlGetAllKVData = db.prepare(`
|
|
148
144
|
SELECT key, value
|
|
149
145
|
FROM kvStore
|
|
@@ -164,16 +160,27 @@ export function makeSwingStoreExporter(dirPath, options = {}) {
|
|
|
164
160
|
yield* transcriptStore.getExportRecords(true);
|
|
165
161
|
yield* bundleStore.getExportRecords();
|
|
166
162
|
}
|
|
163
|
+
harden(getExportData);
|
|
167
164
|
|
|
168
|
-
/**
|
|
169
|
-
|
|
170
|
-
* @yields {string}
|
|
171
|
-
*/
|
|
172
|
-
async function* getArtifactNames() {
|
|
165
|
+
/** @yields {string} */
|
|
166
|
+
async function* artifactNames() {
|
|
173
167
|
yield* snapStore.getArtifactNames(artifactMode);
|
|
174
168
|
yield* transcriptStore.getArtifactNames(artifactMode);
|
|
175
169
|
yield* bundleStore.getArtifactNames();
|
|
176
170
|
}
|
|
171
|
+
harden(artifactNames);
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @returns {AsyncIterableIterator<string>}
|
|
175
|
+
*/
|
|
176
|
+
function getArtifactNames() {
|
|
177
|
+
if (artifactMode !== 'debug') {
|
|
178
|
+
// throw if this DB will not be able to yield all the desired artifacts
|
|
179
|
+
const internal = { snapStore, bundleStore, transcriptStore };
|
|
180
|
+
assertComplete(internal, artifactMode);
|
|
181
|
+
}
|
|
182
|
+
return artifactNames();
|
|
183
|
+
}
|
|
177
184
|
|
|
178
185
|
/**
|
|
179
186
|
* @param {string} name
|
package/src/importer.js
CHANGED
|
@@ -17,10 +17,10 @@ import { assertComplete } from './assertComplete.js';
|
|
|
17
17
|
* returned swingStore is not suitable for execution, and thus only contains
|
|
18
18
|
* the host facet for committing the populated swingStore.
|
|
19
19
|
*
|
|
20
|
-
* @param {import('./exporter').SwingStoreExporter} exporter
|
|
20
|
+
* @param {import('./exporter.js').SwingStoreExporter} exporter
|
|
21
21
|
* @param {string | null} [dirPath]
|
|
22
22
|
* @param {ImportSwingStoreOptions} [options]
|
|
23
|
-
* @returns {Promise<Pick<import('./swingStore').SwingStore, 'hostStorage' | 'debug'>>}
|
|
23
|
+
* @returns {Promise<Pick<import('./swingStore.js').SwingStore, 'hostStorage' | 'debug'>>}
|
|
24
24
|
*/
|
|
25
25
|
export async function importSwingStore(exporter, dirPath = null, options = {}) {
|
|
26
26
|
if (dirPath && typeof dirPath !== 'string') {
|
package/src/internal.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Fail, q } from '@agoric/assert';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @typedef { import('./snapStore').SnapStoreInternal } SnapStoreInternal
|
|
5
|
-
* @typedef { import('./transcriptStore').TranscriptStoreInternal } TranscriptStoreInternal
|
|
6
|
-
* @typedef { import('./bundleStore').BundleStoreInternal } BundleStoreInternal
|
|
4
|
+
* @typedef { import('./snapStore.js').SnapStoreInternal } SnapStoreInternal
|
|
5
|
+
* @typedef { import('./transcriptStore.js').TranscriptStoreInternal } TranscriptStoreInternal
|
|
6
|
+
* @typedef { import('./bundleStore.js').BundleStoreInternal } BundleStoreInternal
|
|
7
7
|
*
|
|
8
8
|
* @typedef {{
|
|
9
9
|
* transcriptStore: TranscriptStoreInternal,
|
package/src/repairMetadata.js
CHANGED
|
@@ -25,7 +25,7 @@ import { assertComplete } from './assertComplete.js';
|
|
|
25
25
|
* `hostStorage.commit()` when they are ready.
|
|
26
26
|
*
|
|
27
27
|
* @param {import('./internal.js').SwingStoreInternal} internal
|
|
28
|
-
* @param {import('./exporter').SwingStoreExporter} exporter
|
|
28
|
+
* @param {import('./exporter.js').SwingStoreExporter} exporter
|
|
29
29
|
* @returns {Promise<void>}
|
|
30
30
|
*/
|
|
31
31
|
export async function doRepairMetadata(internal, exporter) {
|
package/src/snapStore.js
CHANGED
|
@@ -4,7 +4,10 @@ import { finished as finishedCallback, PassThrough, Readable } from 'stream';
|
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import { createGzip, createGunzip } from 'zlib';
|
|
6
6
|
import { Fail, q } from '@agoric/assert';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
aggregateTryFinally,
|
|
9
|
+
PromiseAllOrErrors,
|
|
10
|
+
} from '@agoric/internal/src/node/utils.js';
|
|
8
11
|
import { buffer } from './util.js';
|
|
9
12
|
|
|
10
13
|
/**
|
|
@@ -25,12 +28,11 @@ import { buffer } from './util.js';
|
|
|
25
28
|
*/
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
|
-
* @
|
|
29
|
-
* @typedef { import('./exporter').AnyIterableIterator<T> } AnyIterableIterator<T>
|
|
31
|
+
* @import {AnyIterableIterator} from './exporter.js'
|
|
30
32
|
*/
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
|
-
* @typedef { import('./exporter').SwingStoreExporter } SwingStoreExporter
|
|
35
|
+
* @typedef { import('./exporter.js').SwingStoreExporter } SwingStoreExporter
|
|
34
36
|
* @typedef { import('./internal.js').ArtifactMode } ArtifactMode
|
|
35
37
|
*
|
|
36
38
|
* @typedef {{
|
|
@@ -306,6 +308,7 @@ export function makeSnapStore(
|
|
|
306
308
|
const snapshotReader = gzReader.pipe(unzipper);
|
|
307
309
|
yield* snapshotReader;
|
|
308
310
|
}
|
|
311
|
+
harden(exporter);
|
|
309
312
|
return exporter();
|
|
310
313
|
}
|
|
311
314
|
|
|
@@ -333,17 +336,18 @@ export function makeSnapStore(
|
|
|
333
336
|
snapReader.pipe(hashStream);
|
|
334
337
|
snapReader.pipe(output);
|
|
335
338
|
|
|
339
|
+
await null;
|
|
336
340
|
try {
|
|
337
341
|
yield* output;
|
|
338
342
|
} finally {
|
|
339
343
|
gzReader.destroy();
|
|
340
|
-
// eslint-disable-next-line @jessie.js/no-nested-await
|
|
341
344
|
await finished(gzReader);
|
|
342
345
|
const hash = hashStream.digest('hex');
|
|
343
346
|
hash === snapshotID ||
|
|
344
347
|
Fail`actual hash ${q(hash)} !== expected ${q(snapshotID)}`;
|
|
345
348
|
}
|
|
346
349
|
}
|
|
350
|
+
harden(loadSnapshot);
|
|
347
351
|
|
|
348
352
|
const sqlDeleteVatSnapshots = db.prepare(`
|
|
349
353
|
DELETE FROM snapshots
|
|
@@ -481,6 +485,7 @@ export function makeSnapStore(
|
|
|
481
485
|
}
|
|
482
486
|
}
|
|
483
487
|
}
|
|
488
|
+
harden(getExportRecords);
|
|
484
489
|
|
|
485
490
|
async function* getArtifactNames(artifactMode) {
|
|
486
491
|
for (const rec of sqlGetAvailableSnapshots.iterate(1)) {
|
|
@@ -492,6 +497,7 @@ export function makeSnapStore(
|
|
|
492
497
|
}
|
|
493
498
|
}
|
|
494
499
|
}
|
|
500
|
+
harden(getArtifactNames);
|
|
495
501
|
|
|
496
502
|
const sqlAddSnapshotRecord = db.prepare(`
|
|
497
503
|
INSERT INTO snapshots (vatID, snapPos, hash, inUse)
|
|
@@ -640,6 +646,7 @@ export function makeSnapStore(
|
|
|
640
646
|
function* listAllSnapshots() {
|
|
641
647
|
yield* sqlListAllSnapshots.iterate();
|
|
642
648
|
}
|
|
649
|
+
harden(listAllSnapshots);
|
|
643
650
|
|
|
644
651
|
const sqlDumpCurrentSnapshots = db.prepare(`
|
|
645
652
|
SELECT vatID, snapPos, hash, compressedSnapshot, inUse
|
package/src/snapStoreIO.js
CHANGED
package/src/swingStore.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
/* global Buffer */
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
5
|
|
|
6
6
|
import sqlite3 from 'better-sqlite3';
|
|
7
7
|
|
|
@@ -17,18 +17,18 @@ import { makeSnapStoreIO } from './snapStoreIO.js';
|
|
|
17
17
|
import { doRepairMetadata } from './repairMetadata.js';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* @typedef { import('./kvStore').KVStore } KVStore
|
|
20
|
+
* @typedef { import('./kvStore.js').KVStore } KVStore
|
|
21
21
|
*
|
|
22
|
-
* @typedef { import('./snapStore').SnapStore } SnapStore
|
|
23
|
-
* @typedef { import('./snapStore').SnapshotResult } SnapshotResult
|
|
22
|
+
* @typedef { import('./snapStore.js').SnapStore } SnapStore
|
|
23
|
+
* @typedef { import('./snapStore.js').SnapshotResult } SnapshotResult
|
|
24
24
|
*
|
|
25
|
-
* @typedef { import('./transcriptStore').TranscriptStore } TranscriptStore
|
|
26
|
-
* @typedef { import('./transcriptStore').TranscriptStoreDebug } TranscriptStoreDebug
|
|
25
|
+
* @typedef { import('./transcriptStore.js').TranscriptStore } TranscriptStore
|
|
26
|
+
* @typedef { import('./transcriptStore.js').TranscriptStoreDebug } TranscriptStoreDebug
|
|
27
27
|
*
|
|
28
|
-
* @typedef { import('./bundleStore').BundleStore } BundleStore
|
|
29
|
-
* @typedef { import('./bundleStore').BundleStoreDebug } BundleStoreDebug
|
|
28
|
+
* @typedef { import('./bundleStore.js').BundleStore } BundleStore
|
|
29
|
+
* @typedef { import('./bundleStore.js').BundleStoreDebug } BundleStoreDebug
|
|
30
30
|
*
|
|
31
|
-
* @typedef { import('./exporter').KVPair } KVPair
|
|
31
|
+
* @typedef { import('./exporter.js').KVPair } KVPair
|
|
32
32
|
*
|
|
33
33
|
* @typedef {{
|
|
34
34
|
* kvStore: KVStore, // a key-value API object to load and store data on behalf of the kernel
|
|
@@ -49,7 +49,7 @@ import { doRepairMetadata } from './repairMetadata.js';
|
|
|
49
49
|
* close: () => Promise<void>, // shutdown the store, abandoning any uncommitted changes
|
|
50
50
|
* diskUsage?: () => number, // optional stats method
|
|
51
51
|
* setExportCallback: (cb: (updates: KVPair[]) => void) => void, // Set a callback invoked by swingStore when new serializable data is available for export
|
|
52
|
-
* repairMetadata: (exporter: import('./exporter').SwingStoreExporter) => Promise<void>,
|
|
52
|
+
* repairMetadata: (exporter: import('./exporter.js').SwingStoreExporter) => Promise<void>,
|
|
53
53
|
* }} SwingStoreHostStorage
|
|
54
54
|
*/
|
|
55
55
|
|
|
@@ -543,6 +543,10 @@ export function makeSwingStore(dirPath, forceReset, options = {}) {
|
|
|
543
543
|
});
|
|
544
544
|
}
|
|
545
545
|
|
|
546
|
+
function getDatabase() {
|
|
547
|
+
return db;
|
|
548
|
+
}
|
|
549
|
+
|
|
546
550
|
const transcriptStorePublic = {
|
|
547
551
|
initTranscript: transcriptStore.initTranscript,
|
|
548
552
|
rolloverSpan: transcriptStore.rolloverSpan,
|
|
@@ -592,6 +596,7 @@ export function makeSwingStore(dirPath, forceReset, options = {}) {
|
|
|
592
596
|
const debug = {
|
|
593
597
|
serialize,
|
|
594
598
|
dump,
|
|
599
|
+
getDatabase,
|
|
595
600
|
};
|
|
596
601
|
|
|
597
602
|
return harden({
|
package/src/transcriptStore.js
CHANGED
|
@@ -43,6 +43,7 @@ import { createSHA256 } from './hasher.js';
|
|
|
43
43
|
function* empty() {
|
|
44
44
|
// Yield nothing
|
|
45
45
|
}
|
|
46
|
+
harden(empty);
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
49
|
* @param {number} position
|
|
@@ -158,6 +159,7 @@ export function makeTranscriptStore(
|
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
}
|
|
162
|
+
harden(readFullVatTranscript);
|
|
161
163
|
|
|
162
164
|
function spanArtifactName(rec) {
|
|
163
165
|
return `transcript.${rec.vatID}.${rec.startPos}.${rec.endPos}`;
|
|
@@ -393,6 +395,7 @@ export function makeTranscriptStore(
|
|
|
393
395
|
}
|
|
394
396
|
}
|
|
395
397
|
}
|
|
398
|
+
harden(getExportRecords);
|
|
396
399
|
|
|
397
400
|
const sqlCountSpanItems = db.prepare(`
|
|
398
401
|
SELECT COUNT(*) FROM transcriptItems
|
|
@@ -447,6 +450,7 @@ export function makeTranscriptStore(
|
|
|
447
450
|
}
|
|
448
451
|
}
|
|
449
452
|
}
|
|
453
|
+
harden(getArtifactNames);
|
|
450
454
|
|
|
451
455
|
const sqlGetSpanEndPos = db.prepare(`
|
|
452
456
|
SELECT endPos
|
|
@@ -501,6 +505,7 @@ export function makeTranscriptStore(
|
|
|
501
505
|
expectedCount,
|
|
502
506
|
)})`;
|
|
503
507
|
}
|
|
508
|
+
harden(reader);
|
|
504
509
|
|
|
505
510
|
if (startPos === endPos) {
|
|
506
511
|
return empty();
|
|
@@ -541,6 +546,7 @@ export function makeTranscriptStore(
|
|
|
541
546
|
yield Buffer.from(`${entry}\n`);
|
|
542
547
|
}
|
|
543
548
|
}
|
|
549
|
+
harden(exportSpan);
|
|
544
550
|
|
|
545
551
|
const sqlAddItem = db.prepare(`
|
|
546
552
|
INSERT INTO transcriptItems (vatID, item, position, incarnation)
|
package/src/util.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Buffer } from 'buffer';
|
|
|
6
6
|
* 'stream/consumers' package, which unfortunately only exists in newer versions
|
|
7
7
|
* of Node.
|
|
8
8
|
*
|
|
9
|
-
* @param {import('./exporter').AnyIterable<Uint8Array>} inStream
|
|
9
|
+
* @param {import('./exporter.js').AnyIterable<Uint8Array>} inStream
|
|
10
10
|
*/
|
|
11
11
|
export const buffer = async inStream => {
|
|
12
12
|
const chunks = [];
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
import '@endo/init/debug.js';
|
|
3
2
|
import test from 'ava';
|
|
4
3
|
import tmp from 'tmp';
|
|
5
4
|
import { Buffer } from 'buffer';
|
|
@@ -162,7 +161,7 @@ test('unknown format', t => {
|
|
|
162
161
|
const { kernelStorage } = initSwingStore();
|
|
163
162
|
const { bundleStore } = kernelStorage;
|
|
164
163
|
const unknownID = 'b1999-whoa-futuristic';
|
|
165
|
-
/** @
|
|
164
|
+
/** @import {Bundle} from '../src/bundleStore.js' */
|
|
166
165
|
t.throws(() => bundleStore.addBundle(unknownID, /** @type {Bundle} */ ({})), {
|
|
167
166
|
message: /unsupported BundleID/,
|
|
168
167
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import test from 'ava';
|
|
3
|
-
import '@endo/init/debug.js';
|
|
4
3
|
import { Buffer } from 'node:buffer';
|
|
5
4
|
import { initSwingStore } from '../src/swingStore.js';
|
|
6
5
|
|
|
7
6
|
async function* getSnapshotStream() {
|
|
8
7
|
yield Buffer.from('abc');
|
|
9
8
|
}
|
|
9
|
+
harden(getSnapshotStream);
|
|
10
10
|
|
|
11
11
|
test('delete snapshots with export callback', async t => {
|
|
12
12
|
const exportLog = [];
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import '@endo/init/debug.js';
|
|
2
|
-
|
|
3
1
|
import test from 'ava';
|
|
4
2
|
|
|
5
3
|
import { buffer } from '../src/util.js';
|
|
@@ -83,7 +81,7 @@ const exportTest = test.macro(async (t, mode) => {
|
|
|
83
81
|
// historical transcript spans, and no historical snapshots
|
|
84
82
|
|
|
85
83
|
assert.typeof(mode, 'string');
|
|
86
|
-
/** @
|
|
84
|
+
/** @import {ArtifactMode} from '../src/internal.js' */
|
|
87
85
|
let artifactMode = /** @type {ArtifactMode} */ (mode);
|
|
88
86
|
if (mode === 'debug-on-pruned') {
|
|
89
87
|
artifactMode = 'debug';
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import '@endo/init/debug.js';
|
|
4
3
|
import { Buffer } from 'node:buffer';
|
|
5
4
|
|
|
6
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
7
5
|
import test from 'ava';
|
|
8
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
9
6
|
import tmp from 'tmp';
|
|
10
7
|
import bundleSource from '@endo/bundle-source';
|
|
11
8
|
|
|
@@ -55,7 +52,7 @@ async function embundle(filename) {
|
|
|
55
52
|
const bundleFile = new URL(filename, import.meta.url).pathname;
|
|
56
53
|
const bundle = await bundleSource(bundleFile);
|
|
57
54
|
const bundleID = `b1-${bundle.endoZipBase64Sha512}`;
|
|
58
|
-
return [bundleID, bundle];
|
|
55
|
+
return /** @type {const} */ ([bundleID, bundle]);
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
function actLikeAVatRunningACrank(vat, ks, crank, doFail) {
|
|
@@ -86,6 +83,7 @@ async function fakeAVatSnapshot(vat, ks) {
|
|
|
86
83
|
async function* getSnapshotStream() {
|
|
87
84
|
yield Buffer.from(`snapshot of vat ${vat.vatID} as of ${vat.endPos}`);
|
|
88
85
|
}
|
|
86
|
+
harden(getSnapshotStream);
|
|
89
87
|
await ks.snapStore.saveSnapshot(vat.vatID, vat.endPos, getSnapshotStream());
|
|
90
88
|
ks.transcriptStore.addItem(vat.vatID, 'save-snapshot');
|
|
91
89
|
vat.endPos += 1;
|
|
@@ -122,7 +120,6 @@ test('crank abort leaves no debris in export log', async t => {
|
|
|
122
120
|
crankNum % 3 === 0,
|
|
123
121
|
);
|
|
124
122
|
}
|
|
125
|
-
// eslint-disable-next-line no-await-in-loop
|
|
126
123
|
await ssOut.hostStorage.commit();
|
|
127
124
|
}
|
|
128
125
|
|
|
@@ -218,22 +215,21 @@ async function testExportImport(
|
|
|
218
215
|
actLikeAVatRunningACrank(vat, kernelStorage, crankNum);
|
|
219
216
|
}
|
|
220
217
|
if (block < 3) {
|
|
221
|
-
// eslint-disable-next-line no-await-in-loop
|
|
222
218
|
await fakeAVatSnapshot(vats[block % 2], kernelStorage);
|
|
223
219
|
}
|
|
224
|
-
// eslint-disable-next-line no-await-in-loop
|
|
225
220
|
await ssOut.hostStorage.commit();
|
|
226
221
|
}
|
|
227
222
|
|
|
223
|
+
// the exporter may be broken (the swingstore may be incomplete), but that is
|
|
224
|
+
// signaled during `getArtifactNames()`, not during construction (which must
|
|
225
|
+
// finish quickly, without scanning the whole DB)
|
|
226
|
+
const exporter = makeSwingStoreExporter(dbDir, { artifactMode: exportMode });
|
|
227
|
+
|
|
228
228
|
const incomplete = 'incomplete archival transcript: 3 vs 12';
|
|
229
|
-
function doExport() {
|
|
230
|
-
return makeSwingStoreExporter(dbDir, { artifactMode: exportMode });
|
|
231
|
-
}
|
|
232
229
|
if (failureMode === 'export') {
|
|
233
|
-
await t.throws(
|
|
230
|
+
await t.throws(() => exporter.getArtifactNames(), { message: incomplete });
|
|
234
231
|
return;
|
|
235
232
|
}
|
|
236
|
-
const exporter = doExport();
|
|
237
233
|
|
|
238
234
|
const exportData = [];
|
|
239
235
|
for await (const elem of exporter.getExportData()) {
|
package/test/exports.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { makeB0ID } from './util.js';
|
|
3
|
+
|
|
4
|
+
export const snapshotData = 'snapshot data';
|
|
5
|
+
// this snapHash was computed manually
|
|
6
|
+
export const snapHash =
|
|
7
|
+
'e7dee7266896538616b630a5da40a90e007726a383e005a9c9c5dd0c2daf9329';
|
|
8
|
+
|
|
9
|
+
/** @type {import('../src/bundleStore.js').Bundle} */
|
|
10
|
+
export const bundle0 = { moduleFormat: 'nestedEvaluate', source: '1+1' };
|
|
11
|
+
export const bundle0ID = makeB0ID(bundle0);
|
|
12
|
+
|
|
13
|
+
export function buildData() {
|
|
14
|
+
// build an export manually
|
|
15
|
+
const exportData = new Map();
|
|
16
|
+
const artifacts = new Map();
|
|
17
|
+
|
|
18
|
+
// shadow kvStore
|
|
19
|
+
exportData.set('kv.key1', 'value1');
|
|
20
|
+
|
|
21
|
+
// now add artifacts and metadata in pairs
|
|
22
|
+
|
|
23
|
+
artifacts.set(`bundle.${bundle0ID}`, JSON.stringify(bundle0));
|
|
24
|
+
exportData.set(`bundle.${bundle0ID}`, bundle0ID);
|
|
25
|
+
|
|
26
|
+
const sbase = { vatID: 'v1', hash: snapHash, inUse: 0 };
|
|
27
|
+
const tbase = { vatID: 'v1', startPos: 0, isCurrent: 0, incarnation: 1 };
|
|
28
|
+
const addTS = (key, obj) =>
|
|
29
|
+
exportData.set(key, JSON.stringify({ ...tbase, ...obj }));
|
|
30
|
+
const t0hash =
|
|
31
|
+
'5bee0f44eca02f23eab03703e84ed2647d5d117fed99e1c30a3b424b7f082ab9';
|
|
32
|
+
const t2hash =
|
|
33
|
+
'57152efdd7fdf75c03371d2b4f1088d5bf3eae7fe643babce527ff81df38998c';
|
|
34
|
+
const t5hash =
|
|
35
|
+
'1947001e78e01bd1e773feb22b4ffc530447373b9de9274d5d5fbda3f23dbf2b';
|
|
36
|
+
const t8hash =
|
|
37
|
+
'e6b42c6a3fb94285a93162f25a9fc0145fd4c5bb144917dc572c50ae2d02ee69';
|
|
38
|
+
|
|
39
|
+
addTS(`transcript.v1.0`, { incarnation: 0, endPos: 2, hash: t0hash });
|
|
40
|
+
artifacts.set(`transcript.v1.0.2`, 'start-worker\nshutdown-worker\n');
|
|
41
|
+
|
|
42
|
+
addTS(`transcript.v1.2`, { startPos: 2, endPos: 5, hash: t2hash });
|
|
43
|
+
artifacts.set(
|
|
44
|
+
`transcript.v1.2.5`,
|
|
45
|
+
'start-worker\ndelivery1\nsave-snapshot\n',
|
|
46
|
+
);
|
|
47
|
+
exportData.set(`snapshot.v1.4`, JSON.stringify({ ...sbase, snapPos: 4 }));
|
|
48
|
+
artifacts.set(`snapshot.v1.4`, snapshotData);
|
|
49
|
+
|
|
50
|
+
addTS(`transcript.v1.5`, { startPos: 5, endPos: 8, hash: t5hash });
|
|
51
|
+
artifacts.set(
|
|
52
|
+
'transcript.v1.5.8',
|
|
53
|
+
'load-snapshot\ndelivery2\nsave-snapshot\n',
|
|
54
|
+
);
|
|
55
|
+
exportData.set(
|
|
56
|
+
`snapshot.v1.7`,
|
|
57
|
+
JSON.stringify({ ...sbase, snapPos: 7, inUse: 1 }),
|
|
58
|
+
);
|
|
59
|
+
artifacts.set(`snapshot.v1.7`, snapshotData);
|
|
60
|
+
|
|
61
|
+
artifacts.set('transcript.v1.8.10', 'load-snapshot\ndelivery3\n');
|
|
62
|
+
exportData.set(`snapshot.v1.current`, 'snapshot.v1.7');
|
|
63
|
+
addTS(`transcript.v1.current`, {
|
|
64
|
+
startPos: 8,
|
|
65
|
+
endPos: 10,
|
|
66
|
+
isCurrent: 1,
|
|
67
|
+
hash: t8hash,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return { exportData, artifacts, t0hash, t2hash, t5hash, t8hash };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param { Map<string, string | null> } exportData
|
|
75
|
+
* @param { Map<string, string> } artifacts
|
|
76
|
+
*/
|
|
77
|
+
export function makeExporter(exportData, artifacts) {
|
|
78
|
+
return {
|
|
79
|
+
getHostKV(_key) {
|
|
80
|
+
return undefined;
|
|
81
|
+
},
|
|
82
|
+
async *getExportData() {
|
|
83
|
+
for (const [key, value] of exportData.entries()) {
|
|
84
|
+
/** @type { import('../src/exporter.js').KVPair } */
|
|
85
|
+
const pair = [key, value];
|
|
86
|
+
yield pair;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
async *getArtifactNames() {
|
|
90
|
+
for (const name of artifacts.keys()) {
|
|
91
|
+
yield name;
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
async *getArtifact(name) {
|
|
95
|
+
const data = artifacts.get(name);
|
|
96
|
+
assert(data, `missing artifact ${name}`);
|
|
97
|
+
yield Buffer.from(data);
|
|
98
|
+
},
|
|
99
|
+
// eslint-disable-next-line no-empty-function
|
|
100
|
+
async close() {},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import '@endo/init/debug.js';
|
|
4
|
-
|
|
5
3
|
import path from 'path';
|
|
6
4
|
import { createGunzip } from 'zlib';
|
|
7
5
|
import { Readable } from 'stream';
|
|
@@ -14,7 +12,15 @@ import { decodeBase64 } from '@endo/base64';
|
|
|
14
12
|
import { buffer } from '../src/util.js';
|
|
15
13
|
import { importSwingStore, makeSwingStoreExporter } from '../src/index.js';
|
|
16
14
|
|
|
17
|
-
import { tmpDir
|
|
15
|
+
import { tmpDir } from './util.js';
|
|
16
|
+
import {
|
|
17
|
+
buildData,
|
|
18
|
+
bundle0,
|
|
19
|
+
bundle0ID,
|
|
20
|
+
makeExporter,
|
|
21
|
+
snapHash,
|
|
22
|
+
snapshotData,
|
|
23
|
+
} from './exports.js';
|
|
18
24
|
|
|
19
25
|
const rank = {
|
|
20
26
|
operational: 1,
|
|
@@ -23,15 +29,6 @@ const rank = {
|
|
|
23
29
|
debug: 4,
|
|
24
30
|
};
|
|
25
31
|
|
|
26
|
-
const snapshotData = 'snapshot data';
|
|
27
|
-
// this snapHash was computed manually
|
|
28
|
-
const snapHash =
|
|
29
|
-
'e7dee7266896538616b630a5da40a90e007726a383e005a9c9c5dd0c2daf9329';
|
|
30
|
-
|
|
31
|
-
/** @type {import('../src/bundleStore.js').Bundle} */
|
|
32
|
-
const bundle0 = { moduleFormat: 'nestedEvaluate', source: '1+1' };
|
|
33
|
-
const bundle0ID = makeB0ID(bundle0);
|
|
34
|
-
|
|
35
32
|
function convert(orig) {
|
|
36
33
|
const bundles = Object.fromEntries(
|
|
37
34
|
Object.entries(orig.bundles).map(([bundleID, encBundle]) => {
|
|
@@ -44,41 +41,6 @@ function convert(orig) {
|
|
|
44
41
|
return { ...orig, bundles };
|
|
45
42
|
}
|
|
46
43
|
|
|
47
|
-
/**
|
|
48
|
-
* @typedef { import('../src/exporter').KVPair } KVPair
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* @param { Map<string, string | null> } exportData
|
|
53
|
-
* @param { Map<string, string> } artifacts
|
|
54
|
-
*/
|
|
55
|
-
export function makeExporter(exportData, artifacts) {
|
|
56
|
-
return {
|
|
57
|
-
getHostKV(_key) {
|
|
58
|
-
return undefined;
|
|
59
|
-
},
|
|
60
|
-
async *getExportData() {
|
|
61
|
-
for (const [key, value] of exportData.entries()) {
|
|
62
|
-
/** @type { KVPair } */
|
|
63
|
-
const pair = [key, value];
|
|
64
|
-
yield pair;
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
async *getArtifactNames() {
|
|
68
|
-
for (const name of artifacts.keys()) {
|
|
69
|
-
yield name;
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
async *getArtifact(name) {
|
|
73
|
-
const data = artifacts.get(name);
|
|
74
|
-
assert(data, `missing artifact ${name}`);
|
|
75
|
-
yield Buffer.from(data);
|
|
76
|
-
},
|
|
77
|
-
// eslint-disable-next-line no-empty-function
|
|
78
|
-
async close() {},
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
44
|
test('import empty', async t => {
|
|
83
45
|
const [dbDir, cleanup] = await tmpDir('testdb');
|
|
84
46
|
t.teardown(cleanup);
|
|
@@ -95,68 +57,8 @@ test('import empty', async t => {
|
|
|
95
57
|
});
|
|
96
58
|
});
|
|
97
59
|
|
|
98
|
-
export function buildData() {
|
|
99
|
-
// build an export manually
|
|
100
|
-
const exportData = new Map();
|
|
101
|
-
const artifacts = new Map();
|
|
102
|
-
|
|
103
|
-
// shadow kvStore
|
|
104
|
-
exportData.set('kv.key1', 'value1');
|
|
105
|
-
|
|
106
|
-
// now add artifacts and metadata in pairs
|
|
107
|
-
|
|
108
|
-
artifacts.set(`bundle.${bundle0ID}`, JSON.stringify(bundle0));
|
|
109
|
-
exportData.set(`bundle.${bundle0ID}`, bundle0ID);
|
|
110
|
-
|
|
111
|
-
const sbase = { vatID: 'v1', hash: snapHash, inUse: 0 };
|
|
112
|
-
const tbase = { vatID: 'v1', startPos: 0, isCurrent: 0, incarnation: 1 };
|
|
113
|
-
const addTS = (key, obj) =>
|
|
114
|
-
exportData.set(key, JSON.stringify({ ...tbase, ...obj }));
|
|
115
|
-
const t0hash =
|
|
116
|
-
'5bee0f44eca02f23eab03703e84ed2647d5d117fed99e1c30a3b424b7f082ab9';
|
|
117
|
-
const t2hash =
|
|
118
|
-
'57152efdd7fdf75c03371d2b4f1088d5bf3eae7fe643babce527ff81df38998c';
|
|
119
|
-
const t5hash =
|
|
120
|
-
'1947001e78e01bd1e773feb22b4ffc530447373b9de9274d5d5fbda3f23dbf2b';
|
|
121
|
-
const t8hash =
|
|
122
|
-
'e6b42c6a3fb94285a93162f25a9fc0145fd4c5bb144917dc572c50ae2d02ee69';
|
|
123
|
-
|
|
124
|
-
addTS(`transcript.v1.0`, { incarnation: 0, endPos: 2, hash: t0hash });
|
|
125
|
-
artifacts.set(`transcript.v1.0.2`, 'start-worker\nshutdown-worker\n');
|
|
126
|
-
|
|
127
|
-
addTS(`transcript.v1.2`, { startPos: 2, endPos: 5, hash: t2hash });
|
|
128
|
-
artifacts.set(
|
|
129
|
-
`transcript.v1.2.5`,
|
|
130
|
-
'start-worker\ndelivery1\nsave-snapshot\n',
|
|
131
|
-
);
|
|
132
|
-
exportData.set(`snapshot.v1.4`, JSON.stringify({ ...sbase, snapPos: 4 }));
|
|
133
|
-
artifacts.set(`snapshot.v1.4`, snapshotData);
|
|
134
|
-
|
|
135
|
-
addTS(`transcript.v1.5`, { startPos: 5, endPos: 8, hash: t5hash });
|
|
136
|
-
artifacts.set(
|
|
137
|
-
'transcript.v1.5.8',
|
|
138
|
-
'load-snapshot\ndelivery2\nsave-snapshot\n',
|
|
139
|
-
);
|
|
140
|
-
exportData.set(
|
|
141
|
-
`snapshot.v1.7`,
|
|
142
|
-
JSON.stringify({ ...sbase, snapPos: 7, inUse: 1 }),
|
|
143
|
-
);
|
|
144
|
-
artifacts.set(`snapshot.v1.7`, snapshotData);
|
|
145
|
-
|
|
146
|
-
artifacts.set('transcript.v1.8.10', 'load-snapshot\ndelivery3\n');
|
|
147
|
-
exportData.set(`snapshot.v1.current`, 'snapshot.v1.7');
|
|
148
|
-
addTS(`transcript.v1.current`, {
|
|
149
|
-
startPos: 8,
|
|
150
|
-
endPos: 10,
|
|
151
|
-
isCurrent: 1,
|
|
152
|
-
hash: t8hash,
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
return { exportData, artifacts, t0hash, t2hash, t5hash, t8hash };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
60
|
const importTest = test.macro(async (t, mode) => {
|
|
159
|
-
/** @
|
|
61
|
+
/** @import {ArtifactMode} from '../src/internal.js' */
|
|
160
62
|
const artifactMode = /** @type {ArtifactMode} */ (mode);
|
|
161
63
|
|
|
162
64
|
const [dbDir, cleanup] = await tmpDir('testdb');
|
|
@@ -254,6 +156,7 @@ const importTest = test.macro(async (t, mode) => {
|
|
|
254
156
|
...db.prepare('SELECT * FROM transcriptSpans ORDER BY startPos').iterate(),
|
|
255
157
|
];
|
|
256
158
|
t.deepEqual(
|
|
159
|
+
// @ts-expect-error unknown
|
|
257
160
|
spanRows.map(sr => sr.startPos),
|
|
258
161
|
[0, 2, 5, 8],
|
|
259
162
|
);
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import '@endo/init/debug.js';
|
|
4
|
-
|
|
5
3
|
import path from 'path';
|
|
6
4
|
import test from 'ava';
|
|
7
5
|
import sqlite3 from 'better-sqlite3';
|
|
8
6
|
|
|
9
7
|
import { importSwingStore, openSwingStore } from '../src/index.js';
|
|
10
8
|
|
|
11
|
-
import { makeExporter, buildData } from './
|
|
9
|
+
import { makeExporter, buildData } from './exports.js';
|
|
12
10
|
import { tmpDir } from './util.js';
|
|
13
11
|
|
|
14
12
|
test('repair metadata', async t => {
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import '@endo/init/debug.js';
|
|
4
3
|
import { Buffer } from 'node:buffer';
|
|
5
4
|
import zlib from 'zlib';
|
|
6
5
|
import sqlite3 from 'better-sqlite3';
|
|
7
6
|
|
|
8
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
9
7
|
import test from 'ava';
|
|
10
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
11
8
|
import { makeMeasureSeconds } from '@agoric/internal';
|
|
12
9
|
import { makeSnapStore } from '../src/snapStore.js';
|
|
13
10
|
|
|
@@ -29,6 +26,7 @@ function ensureTxn() {}
|
|
|
29
26
|
async function* getSnapshotStream(payload) {
|
|
30
27
|
yield Buffer.from(payload);
|
|
31
28
|
}
|
|
29
|
+
harden(getSnapshotStream);
|
|
32
30
|
|
|
33
31
|
test('compress to cache file; closes snapshot stream', async t => {
|
|
34
32
|
const db = sqlite3(':memory:');
|
|
@@ -83,6 +81,7 @@ test('compress to cache file; closes snapshot stream', async t => {
|
|
|
83
81
|
sqlGetSnapshot.pluck(true);
|
|
84
82
|
const snapshotGZ = sqlGetSnapshot.get(hash);
|
|
85
83
|
t.truthy(snapshotGZ);
|
|
84
|
+
// @ts-expect-error unknown
|
|
86
85
|
const contents = zlib.gunzipSync(snapshotGZ);
|
|
87
86
|
t.is(contents.toString(), 'abc', 'gunzip(contents) matches original');
|
|
88
87
|
const logInfo = { vatID: 'fakeVatID', ...exportInfo };
|
|
@@ -103,7 +102,6 @@ test('snapStore prepare / commit delete is robust', async t => {
|
|
|
103
102
|
|
|
104
103
|
const hashes = [];
|
|
105
104
|
for (let i = 0; i < 5; i += 1) {
|
|
106
|
-
// eslint-disable-next-line no-await-in-loop
|
|
107
105
|
const { hash } = await store.saveSnapshot(
|
|
108
106
|
'fakeVatID2',
|
|
109
107
|
i,
|
|
@@ -133,7 +131,6 @@ test('snapStore prepare / commit delete is robust', async t => {
|
|
|
133
131
|
t.is(sqlCountSnapshots.get(), 0);
|
|
134
132
|
|
|
135
133
|
for (let i = 0; i < 5; i += 1) {
|
|
136
|
-
// eslint-disable-next-line no-await-in-loop
|
|
137
134
|
const { hash } = await store.saveSnapshot(
|
|
138
135
|
'fakeVatID4',
|
|
139
136
|
i,
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import '@endo/init/debug.js';
|
|
4
|
-
|
|
5
3
|
import tmp from 'tmp';
|
|
6
4
|
import test from 'ava';
|
|
7
5
|
|
|
@@ -32,7 +30,7 @@ async function embundle(filename) {
|
|
|
32
30
|
const bundleFile = new URL(filename, import.meta.url).pathname;
|
|
33
31
|
const bundle = await bundleSource(bundleFile);
|
|
34
32
|
const bundleID = `b1-${bundle.endoZipBase64Sha512}`;
|
|
35
|
-
return [bundleID, bundle];
|
|
33
|
+
return /** @type {const} */ ([bundleID, bundle]);
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
function* iterate(kvStore, start, end) {
|
|
@@ -49,6 +47,7 @@ function* iterate(kvStore, start, end) {
|
|
|
49
47
|
prev = next;
|
|
50
48
|
}
|
|
51
49
|
}
|
|
50
|
+
harden(iterate);
|
|
52
51
|
|
|
53
52
|
function makeExportLog() {
|
|
54
53
|
const exportLog = [];
|
package/test/util.js
CHANGED