@agoric/swing-store 0.9.1 → 0.9.2-calypso-dev-84eb287.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/docs/data-export.md +116 -74
- package/docs/swingstore.md +52 -0
- package/package.json +25 -16
- package/src/assertComplete.js +21 -0
- package/src/bundleStore.js +141 -48
- package/src/exporter.js +221 -0
- package/src/importer.js +133 -0
- package/src/index.js +11 -0
- package/src/internal.js +22 -0
- package/src/kvStore.js +172 -0
- package/src/repairMetadata.js +67 -0
- package/src/snapStore.js +173 -38
- package/src/snapStoreIO.js +8 -0
- package/src/swingStore.js +94 -597
- package/src/transcriptStore.js +265 -61
- package/src/types.d.ts +15 -0
- package/src/types.js +6 -0
- package/src/util.js +7 -1
- package/test/{test-bundles.js → bundles.test.js} +21 -10
- package/test/{test-deletion.js → deletion.test.js} +1 -1
- package/test/export.test.js +350 -0
- package/test/{test-exportImport.js → exportImport.test.js} +87 -67
- package/test/exports.js +102 -0
- package/test/{test-hasher.js → hasher.test.js} +0 -2
- package/test/import.test.js +413 -0
- package/test/repair-metadata.test.js +143 -0
- package/test/{test-snapstore.js → snapstore.test.js} +2 -5
- package/test/{test-state.js → state.test.js} +36 -3
- package/test/util.js +27 -0
- package/{jsconfig.json → tsconfig.json} +1 -0
package/docs/data-export.md
CHANGED
|
@@ -16,7 +16,7 @@ The SwingStore export protocol defines two stages (effectively two datasets). Th
|
|
|
16
16
|
|
|
17
17
|
Each time a SwingStore API is used to modify the state somehow (e.g. adding/changing/deleting a `kvStore` entry, or pushing a new item on to a transcript), the contents of both datasets may change. New first-stage entries can be created, existing ones may be modified or deleted. And the set of second-stage artifacts may change.
|
|
18
18
|
|
|
19
|
-
These export data/artifact changes can happen when calling into the kernel (e.g. invoking the external API of a device, causing the device code to change its own state or push messages onto the run-queue), or by normal kernel operations as it runs (any time `controller.run()` is executing). When the kernel is idle (after `controller.run()` has completed), the kernel will not make any changes to the SwingStore, and both datasets will be stable.
|
|
19
|
+
These export data/artifact changes can happen when calling into the kernel (e.g. invoking the external API of a device, causing the device code to change its own state or push messages onto the run-queue), or by normal kernel operations as it runs (any time `controller.run()` is executing). When the kernel is idle (after `controller.run()` has completed), and `hostStorage.commit()` is called, the kernel will not make any changes to the SwingStore, and both datasets will be stable.
|
|
20
20
|
|
|
21
21
|
Among other things, the SwingStore records a transcript of deliveries for each vat. The collection of all deliveries to a particular vat since its last heap snapshot was written is called the "current span". For each vat, the first-stage export data will record a single record that remembers the extent and the hash of the current span. This record then refers to a second-stage export artifact that contains the actual transcript contents.
|
|
22
22
|
|
|
@@ -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);
|
|
57
|
-
}
|
|
58
|
-
const exportArtifacts = new Map();
|
|
59
|
-
for (const name of exporter.getArtifactNames()) {
|
|
60
|
-
const reader = exporter.getArtifact(name);
|
|
61
|
-
// reader is an async iterable of Uint8Array, e.g. a stream
|
|
62
|
-
const data = await buffer(reader);
|
|
63
|
-
exportArtifacts.set(name, data);
|
|
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();
|
|
64
55
|
}
|
|
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
|

|
|
@@ -83,6 +94,8 @@ So, to include SwingStore data in this state-sync snapshot, we need a way to get
|
|
|
83
94
|
|
|
84
95
|
To support this, SwingStore has an "incremental export" mode. This is activated when the host application supplies an "export callback" option to the SwingStore instance constructor. Instead of retrieving the entire first-stage export data at the end of the block, the host application will be continuously notified about changes to this data as the kernel executes. The host application can then incorporate those entries into an existing hashed Merkle tree (e.g. the cosmos-sdk IAVL tree), whose root hash is included in the consensus block hash. Every time the callback is given `(key, value)`, the host should add a new (or modify some existing) IAVL entry, using an IAVL key within some range dedicated to the SwingStore first-stage export data. When the callback receives `(key, undefined)` or `(key, null)`, it should delete the entry. In this way, the IAVL tree maintains a "shadow copy" of the first-stage export data at all times, making the contents both covered by the consensus hash, and automatically included in the cosmos-sdk IAVL tree where it will become available to the new validator as it begins to reconstruct the SwingStore.
|
|
85
96
|
|
|
97
|
+
The export callback must be established from the very beginning, so it includes all changes made during kernel initialization.
|
|
98
|
+
|
|
86
99
|
All validator nodes use this export callback, even if they never perform the rest of the export process, to ensure that the consensus state includes the entire first-stage dataset. (Note that the first stage data is generally smaller than the full dataset, making this relatively inexpensive).
|
|
87
100
|
|
|
88
101
|
Then, on the few occasions when the application needs to build a full state-sync snapshot, it can ask the SwingStore (after block commit) for the full set of artifacts that match the most recent commit.
|
|
@@ -90,54 +103,58 @@ Then, on the few occasions when the application needs to build a full state-sync
|
|
|
90
103
|

|
|
91
104
|
|
|
92
105
|
```js
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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();
|
|
120
133
|
}
|
|
121
|
-
|
|
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
|
|
122
151
|
```
|
|
123
152
|
|
|
124
153
|
## Import
|
|
125
154
|
|
|
126
|
-
On other end of the export process is an importer. This is a new host application
|
|
155
|
+
On the other end of the export process is an importer. This is used to restore kernel state, so that a new host application can simply continue mostly as if it had been previously executing. The expectation is that the import and the execution are 2 independent events, and the execution doesn't need to be aware it was imported.
|
|
127
156
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
```js
|
|
131
|
-
// this is done only the first time an instance is created:
|
|
132
|
-
|
|
133
|
-
import { openSwingStore } from '@agoric/swing-store';
|
|
134
|
-
import { initializeSwingset } from '@agoric/swingset-vat';
|
|
135
|
-
const dirPath = './swing-store';
|
|
136
|
-
const { hostStorage, kernelStorage } = openSwingStore(dirPath);
|
|
137
|
-
await initializeSwingset(config, argv, kernelStorage);
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
Once the initial state is created, each time the application is launched, it will build a controller around the existing state:
|
|
157
|
+
For reference, after the initial state is created, each time the application is launched, it builds a controller around the existing state:
|
|
141
158
|
|
|
142
159
|
```js
|
|
143
160
|
import { openSwingStore } from '@agoric/swing-store';
|
|
@@ -148,7 +165,7 @@ const controller = await makeSwingsetController(kernelStorage);
|
|
|
148
165
|
// ... now do things like controller.run(), etc
|
|
149
166
|
```
|
|
150
167
|
|
|
151
|
-
When cloning an existing kernel, the
|
|
168
|
+
When cloning an existing kernel, the host application first imports and commits the restored state using `importSwingStore`. The host application should feed the importer with the export data and artifacts, by passing an object that has the same API as the SwingStore's exporter:
|
|
152
169
|
|
|
153
170
|
```js
|
|
154
171
|
import { importSwingStore } from '@agoric/swing-store';
|
|
@@ -159,11 +176,13 @@ const exporter = {
|
|
|
159
176
|
getArtifact(name) { // return blob of artifact data },
|
|
160
177
|
};
|
|
161
178
|
const { hostStorage } = importSwingStore(exporter, dirPath);
|
|
162
|
-
hostStorage
|
|
163
|
-
|
|
179
|
+
// Update any hostStorage as needed
|
|
180
|
+
await hostStorage.commit();
|
|
181
|
+
await hostStorage.close();
|
|
182
|
+
// now the populated swingstore can be re-opened using `openSwingStore``
|
|
164
183
|
```
|
|
165
184
|
|
|
166
|
-
Once the new SwingStore is fully populated with the previously-exported data, the host application can
|
|
185
|
+
Once the new SwingStore is fully populated with the previously-exported data, the host application can update any host specific state before committing and closing the SwingStore. `importSwingStore` returns only the host facet of the SwingStore instance, as it is not suitable for immediate execution.
|
|
167
186
|
|
|
168
187
|
## Optional / Historical Data
|
|
169
188
|
|
|
@@ -177,18 +196,41 @@ As a result, for each active vat, the first-stage Export Data contains a record
|
|
|
177
196
|
|
|
178
197
|
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.
|
|
179
198
|
|
|
180
|
-
|
|
199
|
+
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:
|
|
200
|
+
|
|
201
|
+
* `operational`: include only the current transcript span and current snapshot for each vat: just the minimum set necessary for current operations
|
|
202
|
+
* `replay`: add all transcript spans for the current incarnation
|
|
203
|
+
* `archival`: add all available transcript spans, even for old incarnations
|
|
204
|
+
* `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
|
|
205
|
+
|
|
206
|
+
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.
|
|
207
|
+
|
|
208
|
+
However the `debug` export mode will never fail: it merely dumps everything in the swingstore, without limits or completeness checks.
|
|
209
|
+
|
|
210
|
+
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.
|
|
211
|
+
|
|
212
|
+
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`.
|
|
213
|
+
|
|
214
|
+
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.
|
|
215
|
+
|
|
216
|
+
While `importSwingStore()`'s options bag accepts the same options as `openSwingStore()`, since it returns only the host facet of a SwingStore, some of these options might not be meaningful, such as `keepTranscripts`.
|
|
217
|
+
|
|
218
|
+
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:
|
|
181
219
|
|
|
182
|
-
|
|
220
|
+
* the original swingstore must not be opened with `{ keepTranscripts: false }`, otherwise the old spans will be pruned immediately
|
|
221
|
+
* the export must use `makeSwingStoreExporter(dirpath, { artifactMode: 'replay'})`, otherwise the export will omit the old spans
|
|
222
|
+
* the import must use `importSwingStore(exporter, dirPath, { artifactMode: 'replay'})`, otherwise the import will ignore the old spans
|
|
223
|
+
* subsequent `openSwingStore` calls must not use `keepTranscripts: false`, otherwise the new swingstore will prune historical spans as new ones are created (during `rolloverSpan`).
|
|
183
224
|
|
|
184
225
|
## Implementation Details
|
|
185
226
|
|
|
186
|
-
SwingStore contains components to
|
|
227
|
+
SwingStore contains components to accommodate all the various kinds of state that the SwingSet kernel needs to store. This currently consists of four portions:
|
|
187
228
|
|
|
188
229
|
* `kvStore`, a general-purpose string/string key-value table
|
|
189
230
|
* `transcriptStore`: append-only vat deliveries, broken into "spans", delimited by heap snapshot events
|
|
190
231
|
* `snapshotStore`: binary blobs containing JS engine heap state, to limit transcript replay depth
|
|
232
|
+
* `bundleStore`: code bundles that can be imported with [@endo/import-bundle](https://www.npmjs.com/package/@endo/import-bundle)
|
|
191
233
|
|
|
192
|
-
Currently, the SwingStore treats transcript spans
|
|
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.
|
|
193
235
|
|
|
194
236
|
If some day we implement an IAVL-like Merkle tree inside SwingStore, and use it to automatically generate a root hash for the `kvStore` at the end of each block, we will replace this (large) shadow copy with a single `kvStoreRootHash` entry, and add a new export artifact to contain the full contents of the kvStore. This reduce the size of the IAVL tree, as well as the rate of IAVL updates during block execution, at the cost of increased CPU and complexity within SwingStore.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# SwingStore Data Model
|
|
2
|
+
|
|
3
|
+
The "SwingStore" provides a database to hold SwingSet kernel state, with an API crafted to help both the kernel and the host application mutate, commit, export, and import this state.
|
|
4
|
+
|
|
5
|
+
The state is broken up into several pieces, or "stores":
|
|
6
|
+
|
|
7
|
+
* `bundleStore`: a string-keyed Bundle-value table, holding source bundles which can be evaluated by `importBundle` to create vats, or new Compartments within a vat
|
|
8
|
+
* `transcriptStore`: records a linear sequence of deliveries and syscalls (with results), collectively known as "transcript entries", for each vat
|
|
9
|
+
* `snapStore`: records one or more XS heap snapshots for each vat, to rebuild a worker more efficiently than replaying all transcript entries from the beginning
|
|
10
|
+
* `kvStore`: a string-keyed string-valued table, which holds everything else. Currently, this holds each vat's c-list and vatstore data, as well as the kernel-wide object and promise tables, and run-queues.
|
|
11
|
+
|
|
12
|
+
## Incarnations, Spans, Snapshots
|
|
13
|
+
|
|
14
|
+
The kernel tracks the state of one or more vats. Each vat's execution is split into "incarnations", which are separated by a "vat upgrade" (a call to `E(vatAdminFacet).upgrade(newBundleCap, options)`, see https://github.com/Agoric/agoric-sdk/blob/master/packages/SwingSet/docs/vat-upgrade.md for details). Each incarnation gets a new worker, which erases the heap state and only retains durable vatstore data across the upgrade. Every active vat has a "current incarnation", and zero or more "historic incarnations". Only the current incarnation is instantiated.
|
|
15
|
+
|
|
16
|
+
Within each incarnation, execution is broken into one or more "spans", with a "current span" and zero or more "historic spans". This breaks up the transcript into corresponding spans.
|
|
17
|
+
|
|
18
|
+
Each historic span ends with a `save-snapshot` entry which records the creation and saving of an XS heap snapshot. The initial span starts with a `start-worker` entry, while all non-initial spans start with a `load-snapshot` entry. The final span of historic incarnations each end with a `shutdown-worker` entry.
|
|
19
|
+
|
|
20
|
+
Each `save-snapshot` entry adds a new snapshot to the `snapStore`, so each vat has zero or more snapshots, of which the last one is called the "current" or "in-use" snapshot, and the earlier ones are called "historical snapshots".
|
|
21
|
+
|
|
22
|
+
(note: the `deliveryNum` counter is scoped to the vat and does not reset at incarnation or span boundaries)
|
|
23
|
+
|
|
24
|
+
## Artifacts
|
|
25
|
+
|
|
26
|
+
The import/export process (using `makeSwingStoreExporter` and `importSwingStore`) defines some number of "artifacts" to contain much of the SwingStore data. Each bundle is a separate artifact, as is each heap snapshot. Each transcript span is a separate artifact (an aggregate of the individual transcript entries comprising that span).
|
|
27
|
+
|
|
28
|
+
During export, the `getArtifactNames()` method provides a list of all available artifacts, while `getArtifact(name)` is used to retrieve the actual data. The import function processes each artifact separately.
|
|
29
|
+
|
|
30
|
+
## Populated vs Pruned
|
|
31
|
+
|
|
32
|
+
For normal operation, the kernel does not require historical incarnations, spans, or snapshots. It only needs the ability to reconstruct a worker for the current incarnation of each vat, which means loading the current snapshot (if any), and replaying the contents of the current transcript span.
|
|
33
|
+
|
|
34
|
+
For this reason, the swingstore must always contain the current transcript span, and the current snapshot (if any), for every vat.
|
|
35
|
+
|
|
36
|
+
However, to save space, historical spans/snapshots might be pruned, by deleting their contents from the database (but retaining the metadata, which includes a hash of the contents for later validation). Historical snapshots are pruned by default (unless `openSwingStore()` is given an options bag with `keepSnapshots: true`). Historical spans are not currently pruned (the `keepTranscripts` option defaults to `true`), but that may change.
|
|
37
|
+
|
|
38
|
+
In addition, `importSwingStore()` can be used to create a SwingStore from data exported out of some other SwingStore. The export-then-import process might result in a pruned DB in one of three ways:
|
|
39
|
+
|
|
40
|
+
* the import-time options might instruct the import process to ignore some of the available data
|
|
41
|
+
* the export-time options might have done the same
|
|
42
|
+
* the original DB was itself already pruned, so the data was not available in the first place
|
|
43
|
+
|
|
44
|
+
In the future, a separate SwingStore API will exist to allow previously-pruned artifacts to be repopulated. Every artifact has a metadata record which *is* included in the export (in the `exportData` section, but separate from the kvStore shadow table entries, see [data-export.md](./data-export.md)), regardless of pruning modes, to ensure that this API can check the integrity of these repopulated artifacts. This reduces the reliance set and trust burden of the repopulation process (we can safely use untrusted artifact providers).
|
|
45
|
+
|
|
46
|
+
When a snapshot is pruned, the `snapshots` SQL table row is modified, replacing its `compressedSnapshot` BLOB with a NULL. The other columns are left alone, especially the `hash` column, which retains the integrity-checking metadata to support a future repopulation.
|
|
47
|
+
|
|
48
|
+
When a transcript span is pruned, the `transcriptSpans` row is left alone, but the collection of `transcriptItems` rows are deleted. Any span for which all the `transcriptItems` rows are present is said to be "populated", while any span that is missing one or more `transcriptItems` rows is said to be "pruned". (There is no good reason for a span to be only partially pruned, but until we compress historical spans into a single row, in some new table, there remains the possibility of partial pruning).
|
|
49
|
+
|
|
50
|
+
During import, we create the metadata first (as the export-data is parsed), then later, we fill in the details as the artifacts are read.
|
|
51
|
+
|
|
52
|
+
Bundles are never pruned, however during import, the `bundles` table will temporarily contain rows whose `bundle` BLOB is NULL.
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/swing-store",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2-calypso-dev-84eb287.0+84eb287",
|
|
4
4
|
"description": "Persistent storage for SwingSet",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "src/
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js"
|
|
9
|
+
},
|
|
7
10
|
"repository": "https://github.com/Agoric/agoric-sdk",
|
|
8
11
|
"author": "Agoric",
|
|
9
12
|
"license": "Apache-2.0",
|
|
@@ -14,23 +17,23 @@
|
|
|
14
17
|
"test:xs": "exit 0",
|
|
15
18
|
"lint-fix": "yarn lint:eslint --fix",
|
|
16
19
|
"lint": "run-s --continue-on-error lint:*",
|
|
17
|
-
"lint:types": "tsc
|
|
20
|
+
"lint:types": "tsc",
|
|
18
21
|
"lint:eslint": "eslint ."
|
|
19
22
|
},
|
|
20
23
|
"dependencies": {
|
|
21
|
-
"@agoric/assert": "
|
|
22
|
-
"@agoric/internal": "
|
|
23
|
-
"@endo/base64": "^0.
|
|
24
|
-
"@endo/bundle-source": "^2.
|
|
25
|
-
"@endo/check-bundle": "^0.
|
|
26
|
-
"@endo/nat": "^
|
|
27
|
-
"better-sqlite3": "^
|
|
24
|
+
"@agoric/assert": "0.6.1-calypso-dev-84eb287.0+84eb287",
|
|
25
|
+
"@agoric/internal": "0.3.3-calypso-dev-84eb287.0+84eb287",
|
|
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"
|
|
28
31
|
},
|
|
29
32
|
"devDependencies": {
|
|
30
|
-
"@endo/init": "^
|
|
31
|
-
"@types/better-sqlite3": "^7.
|
|
32
|
-
"ava": "^5.
|
|
33
|
-
"c8": "^
|
|
33
|
+
"@endo/init": "^1.1.2",
|
|
34
|
+
"@types/better-sqlite3": "^7.6.9",
|
|
35
|
+
"ava": "^5.3.0",
|
|
36
|
+
"c8": "^9.1.0",
|
|
34
37
|
"tmp": "^0.2.1"
|
|
35
38
|
},
|
|
36
39
|
"publishConfig": {
|
|
@@ -38,9 +41,15 @@
|
|
|
38
41
|
},
|
|
39
42
|
"ava": {
|
|
40
43
|
"files": [
|
|
41
|
-
"test
|
|
44
|
+
"test/**/*.test.*"
|
|
45
|
+
],
|
|
46
|
+
"require": [
|
|
47
|
+
"@endo/init/debug.js"
|
|
42
48
|
],
|
|
43
49
|
"timeout": "2m"
|
|
44
50
|
},
|
|
45
|
-
"
|
|
51
|
+
"typeCoverage": {
|
|
52
|
+
"atLeast": 76.31
|
|
53
|
+
},
|
|
54
|
+
"gitHead": "84eb287915c8255d68e845a9b6e10c790f0d54cb"
|
|
46
55
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {import('./internal.js').SwingStoreInternal} internal
|
|
3
|
+
* @param {Omit<import('./internal.js').ArtifactMode, 'debug'>} checkMode
|
|
4
|
+
* @returns {void}
|
|
5
|
+
*/
|
|
6
|
+
export function assertComplete(internal, checkMode) {
|
|
7
|
+
// every bundle must be populated
|
|
8
|
+
internal.bundleStore.assertComplete(checkMode);
|
|
9
|
+
|
|
10
|
+
// every 'isCurrent' transcript span must have all items
|
|
11
|
+
// TODO: every vat with any data must have a isCurrent transcript
|
|
12
|
+
// span
|
|
13
|
+
internal.transcriptStore.assertComplete(checkMode);
|
|
14
|
+
|
|
15
|
+
// every 'inUse' snapshot must be populated
|
|
16
|
+
internal.snapStore.assertComplete(checkMode);
|
|
17
|
+
|
|
18
|
+
// TODO: every isCurrent span that starts with load-snapshot has a
|
|
19
|
+
// matching snapshot (counter-argument: swing-store should not know
|
|
20
|
+
// those details about transcript entries)
|
|
21
|
+
}
|