From c4eeff35d28a0508163c50e3d2b59337afdd0364 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sat, 4 Feb 2023 17:04:12 -0800 Subject: [PATCH] fix: add 'v'/'d' virtual/durable annotations to vrefs This changes the vref format to reveal the ephemeral/virtual/durable status of each object to the kernel. The kernel doesn't normally care, but I'd like to move upgrade-time responsibility for abandoning merely-virtual objects from the vat to the kernel, which requires the kernel be capable of distinguishing them from durable objects. In this format, ephemeral objects exports are `o+NN`, merely-virtual object exports start with `o+vNN` (where `NN` names the KindID, and the vref always has an instance-ID suffix, so like `o+v34/2`), and durable object exports use `o+dNN`. As before, virtual/durable vrefs might include an additional facet-ID suffix: `o+v34/2:1`. Since the kernel doesn't yet care about details of the vref, very little code changed in packages/SwingSet: just a few tests that had stronger opinions about vrefs than they should be having. The kernel's copy of `parseVatSlots` grew the ability to distinguish virtual from durable, but nothing uses it yet. closes #6695 Co-authored-by: Mathieu Hofman <86499+mhofman@users.noreply.github.com> --- packages/SwingSet/docs/c-lists.md | 66 ++++++++ packages/SwingSet/src/lib/parseVatSlots.js | 32 +++- .../virtualObjects/test-representatives.js | 28 ++-- .../docs/liveslots.md | 0 packages/swingset-liveslots/package.json | 1 + .../src/collectionManager.js | 10 +- packages/swingset-liveslots/src/liveslots.js | 24 +-- .../swingset-liveslots/src/parseVatSlots.js | 36 ++++- packages/swingset-liveslots/src/stop-vat.js | 9 +- .../swingset-liveslots/src/vatstore-usage.md | 74 ++++----- .../src/virtualObjectManager.js | 6 +- .../src/virtualReferences.js | 33 ++-- .../swingset-liveslots/src/watchedPromises.js | 4 +- .../swingset-liveslots/test/gc-helpers.js | 4 +- .../test/liveslots-helpers.js | 11 +- .../test/storeGC/test-lifecycle.js | 2 +- .../test/storeGC/test-scalar-store-kind.js | 2 +- .../swingset-liveslots/test/test-baggage.js | 2 +- .../test/test-initial-vrefs.js | 142 ++++++++++++++++++ .../swingset-liveslots/test/test-liveslots.js | 50 +++--- .../test/test-vpid-liveslots.js | 12 +- .../virtual-objects/test-cease-recognition.js | 8 +- .../virtual-objects/test-virtualObjectGC.js | 22 +-- .../test-virtualObjectManager.js | 14 +- .../test-weakcollections-vref-handling.js | 4 +- .../tools/fakeVirtualSupport.js | 6 +- 26 files changed, 428 insertions(+), 174 deletions(-) create mode 100644 packages/SwingSet/docs/c-lists.md rename packages/{SwingSet => swingset-liveslots}/docs/liveslots.md (100%) create mode 100644 packages/swingset-liveslots/test/test-initial-vrefs.js diff --git a/packages/SwingSet/docs/c-lists.md b/packages/SwingSet/docs/c-lists.md new file mode 100644 index 000000000000..1e44b5fac3d7 --- /dev/null +++ b/packages/SwingSet/docs/c-lists.md @@ -0,0 +1,66 @@ +# C-Lists + +SwingSet, like many capability-based systems, uses Capability Lists ("c-lists") to both translate access rights from one domain to another, and to limit those rights to previously-granted objects. + +The SwingSet kernel supports a number of vats, in a star configuration. At the boundary between the vat and each kernel, the kernel hosts a c-list table, which maps the kernel-side reference strings ("krefs") to their vat-side counterparts ("vrefs"). + +| vatID: v4 | +| kref | vref | +| ---- | ------ | +| ko12 | o+0 | +| ko13 | o+d5/1 | +| ko14 | o+v6/1 | +| ko15 | o+v7/1:0 | +| ko16 | o-4 | +| kp7 | p+3 | +| kp8 | p-3 | +| kd4 | d-4 | + +The c-list is currently stored as `kvStore` entries in the kernel's swing-store DB. Each c-list entry gets two kvStore keys, one for each direction. The kref-to-vref mapping gets a key of `${vatID}.c.${kref}`, while the vref-to-kref mapping gets `${vatID}.c.${vref}`. The value is the vref or kref, respectively. (The c-list is also used to track the reachable/recognizable status of the vat's import, so the krev-to-vref direction has additional flag characters in its value). + +Object krefs (`koNN`) point into the kernel object table. Each such object is exported by exactly one vat, and might be imported by one or more other vats. + +Promise krefs (`kpNN`) also have a kernel table. Each unresolved promise has a "decider", who has the authority to resolve it to some value, or reject it with some error data. Unresolved promises also have subscribers (who will be notified when it settles), and a queue of messages (which will be delivered to the eventual resolution). Resolved promises have resolution/rejection data. + +Device krefs (`kdNN`) refer to device nodes, which are exported by exactly one device, and can be imported by vats. (Devices are managed very much like vats, and they have their own c-lists. Only devices can export device nodes, and vats can only import them). + +Each time the kernel sends a message into the vat, the kernel translates the message krefs through the c-list into vrefs. This may cause new "vat imports" to be allocated, adding new entries to the c-list. + +Each time the vat sends a message into the kernel, the kernel is also responsible for translating the message vrefs into krefs. This may cause new "vat exports" to be allocated, adding new entries to the c-list. + +The kernel owns the c-lists: vats cannot read the contents, nor directly modify them (only vat exports cause vat-supplied data to be added). Vats never learn krefs. + +## kref Formats + +Kernel-side krefs are always one of: + +* `koNN` : objects +* `kpNN` : promises +* `kdNN` : device nodes + +## vref Formats + +Vat-side vrefs always start with a type and which-side-allocated-it prefix: + +* `o+` : vat-allocated objects +* `o-` : kernel-allocated objects +* `p+` : vat-allocated promises +* `p-` : kernel-allocated promises +* `d+` : device-allocated device nodes (note: only devices can export device nodes, not vats) +* `d-` : kernel-allocated device nodes + +The suffix of a kernel-allocated vref will just be a numeric identifier: `o-NN`, `p-NN`, and `d-NN`. + +Vat-allocated vrefs are allowed more flexibility in their suffix. The kernel asserts a particular shape with `parseVatSlot()`, but this is meant for catching mistakes, and we expect `parseVatSlot` to become more lenient over time. In particular, liveslots-based vats create virtual-object vrefs with a multi-part `o+TKK/II:FF` format, to track the `T` ephemeral/virtual/durable status, the `KK` Kind ID, the `II` instance ID, and the `FF` facet ID. + +As a result, the kernel generally does not examine exported vrefs too carefully. The one constraint is that the kernel is allowed to examine the virtual-vs-durable status, so it can delete non-durable c-list entries when a vat is upgraded. + +## Vat Imports + +When the kernel sends a new object reference into the vat, the vat is said to "import" this reference. The kernel will allocate a new `o-NN` vref, and populate the vat's c-list with the kref/vref pair. All negative index values are imports, and allocation is owned by the kernel (which manages a counter to keep them distinct). All positive index values are allocated by the vat, so they will never conflict with the kernel's allocations. + +krefs can appear in `dispatch.deliver` deliveries (which send object messages into a vat), or `dispatch.notify` (which inform the vat about the resolution or rejection of a promise they have subscribed to). They can also appear in GC-related deliveries like `dispatch.dropExports`, `dispatch.retireExports`, and `dispatch.retireImports`, where they reference an object that is no longer reachable or recognizable. + +## Vat Exports + +When a vat sends an outbound message with `syscall.send` (or resolves a promise with `syscall.resolve`), it can include both pre-existing object vrefs, and new ones. The new ones must always start with `o+`, to distinguish them from imports. Vats cannot just make up `o-` entries: if the kernel has not granted the vat access to a particular `koNN` kernel object, the kref will not already be present in the c-list, and the vat cannot emit any vref that will translate into that kref. Access is only acquired through inbound deliveries that include the new reference. diff --git a/packages/SwingSet/src/lib/parseVatSlots.js b/packages/SwingSet/src/lib/parseVatSlots.js index 0c1f1e05952e..ab821297d99c 100644 --- a/packages/SwingSet/src/lib/parseVatSlots.js +++ b/packages/SwingSet/src/lib/parseVatSlots.js @@ -21,15 +21,16 @@ import { assert, Fail } from '@agoric/assert'; * subid: Nat, * baseRef: STRING, * facet: Nat, - * virtual: BOOL, // true=>vref designates a virtual object + * virtual: BOOL, // true=>vref designates a "merely virtual" object (not durable, not ephemeral) + * durable: BOOL, // designates a durable (not merely virtual, not ephemeral) * } * * A vref string can take one of the forms: * * T-N * T+N - * T+N/I - * T+N/I:F + * T+DN/I + * T+DN/I:F * * Where: * @@ -42,6 +43,13 @@ import { assert, Fail } from '@agoric/assert'; * (typically an import) or '+ for the vat (typically an export). This is * returned in the `allocatedByVat` property of the result as a boolean. * + * D is the durability status: for object exports ("o+"), this will + * be the letter 'd' for durable objects, the letter 'v' for + * non-durable ("merely virtual") objects, or empty for + * "Remotable" (ephemeral) objects. It is empty for object + * imports ("o-"), and both both imports and exports of all other + * types (promises, devices) + * * N is a decimal integer representing the identity of the referenced entity. * This is returned in the `id` property of the result as a BigInt. * @@ -79,7 +87,7 @@ import { assert, Fail } from '@agoric/assert'; * XXX TODO: The previous comment suggests some renaming is warranted: * * In the current implementation, a vref string may only include decimal digits, - * the letters 'd', 'o', and 'p', and the punctuation characters '+', '-', '/', + * the letters 'd'/'o'/'p'/'v', and the punctuation characters '+', '-', '/', * and ':'. Future evolution of the vref syntax might add more characters to * this set, but the characters '|' and ',' are permanently excluded: '|' is * used (notably by the collection manager) as delimiter in vatstore keys that @@ -113,7 +121,7 @@ export function parseVatSlot(vref) { let allocatedByVat; const typechar = baseRef[0]; const allocchar = baseRef[1]; - const idSuffix = baseRef.slice(2); + let idSuffix = baseRef.slice(2); if (typechar === 'o') { type = 'object'; @@ -133,14 +141,22 @@ export function parseVatSlot(vref) { Fail`invalid vref ${vref}`; } + let virtual = false; + let durable = false; + if (idSuffix.startsWith('d')) { + durable = true; + idSuffix = idSuffix.slice(1); + } else if (idSuffix.startsWith('v')) { + virtual = true; // merely virtual + idSuffix = idSuffix.slice(1); + } const delim = idSuffix.indexOf('/'); let id; let subid; let facet; - let virtual = false; if (delim > 0) { (type === 'object' && allocatedByVat) || Fail`invalid vref ${vref}`; - virtual = true; + virtual || durable || Fail`invalid vref ${vref}`; // subid only exists for virtuals/durables id = Nat(BigInt(idSuffix.substr(0, delim))); subid = Nat(BigInt(idSuffix.slice(delim + 1))); } else { @@ -150,7 +166,7 @@ export function parseVatSlot(vref) { facet = Nat(BigInt(facetStr)); } - return { type, allocatedByVat, virtual, id, subid, baseRef, facet }; + return { type, allocatedByVat, virtual, durable, id, subid, baseRef, facet }; } /** diff --git a/packages/SwingSet/test/virtualObjects/test-representatives.js b/packages/SwingSet/test/virtualObjects/test-representatives.js index f01771aad5c0..f8d8682f3ba0 100644 --- a/packages/SwingSet/test/virtualObjects/test-representatives.js +++ b/packages/SwingSet/test/virtualObjects/test-representatives.js @@ -118,13 +118,13 @@ test.serial('exercise cache', async t => { await doSimple('holdThing', what); } function dataKey(num) { - return `v1.vs.vom.o+10/${num}`; + return `v1.vs.vom.o+v10/${num}`; } function esKey(num) { - return `v1.vs.vom.es.o+10/${num}`; + return `v1.vs.vom.es.o+v10/${num}`; } function rcKey(num) { - return `v1.vs.vom.rc.o+10/${num}`; + return `v1.vs.vom.rc.o+v10/${num}`; } function thingVal(name) { return JSON.stringify({ @@ -367,7 +367,7 @@ test('virtual object gc', async t => { } // prettier-ignore t.deepEqual(remainingVOs, { - [`${v}.vs.baggageID`]: 'o+6/1', + [`${v}.vs.baggageID`]: 'o+d6/1', [`${v}.vs.idCounters`]: '{"exportID":11,"collectionID":5,"promiseID":8}', [`${v}.vs.kindIDID`]: '1', [`${v}.vs.storeKindIDTable`]: @@ -388,17 +388,17 @@ test('virtual object gc', async t => { [`${v}.vs.vc.4.|label`]: 'watchedPromises', [`${v}.vs.vc.4.|nextOrdinal`]: '1', [`${v}.vs.vc.4.|schemata`]: vstr([M.string()]), - [`${v}.vs.vom.es.o+10/3`]: 'r', - [`${v}.vs.vom.o+10/2`]: `{"label":${vstr('thing #2')}}`, - [`${v}.vs.vom.o+10/3`]: `{"label":${vstr('thing #3')}}`, - [`${v}.vs.vom.o+10/8`]: `{"label":${vstr('thing #8')}}`, - [`${v}.vs.vom.o+10/9`]: `{"label":${vstr('thing #9')}}`, - [`${v}.vs.vom.rc.o+6/1`]: '1', - [`${v}.vs.vom.rc.o+6/3`]: '1', - [`${v}.vs.vom.rc.o+6/4`]: '1', + [`${v}.vs.vom.es.o+v10/3`]: 'r', + [`${v}.vs.vom.o+v10/2`]: `{"label":${vstr('thing #2')}}`, + [`${v}.vs.vom.o+v10/3`]: `{"label":${vstr('thing #3')}}`, + [`${v}.vs.vom.o+v10/8`]: `{"label":${vstr('thing #8')}}`, + [`${v}.vs.vom.o+v10/9`]: `{"label":${vstr('thing #9')}}`, + [`${v}.vs.vom.rc.o+d6/1`]: '1', + [`${v}.vs.vom.rc.o+d6/3`]: '1', + [`${v}.vs.vom.rc.o+d6/4`]: '1', [`${v}.vs.vom.vkind.10`]: '{"kindID":"10","tag":"thing"}', - [`${v}.vs.watchedPromiseTableID`]: 'o+6/4', - [`${v}.vs.watcherTableID`]: 'o+6/3', + [`${v}.vs.watchedPromiseTableID`]: 'o+d6/4', + [`${v}.vs.watcherTableID`]: 'o+d6/3', }); }); diff --git a/packages/SwingSet/docs/liveslots.md b/packages/swingset-liveslots/docs/liveslots.md similarity index 100% rename from packages/SwingSet/docs/liveslots.md rename to packages/swingset-liveslots/docs/liveslots.md diff --git a/packages/swingset-liveslots/package.json b/packages/swingset-liveslots/package.json index 37cc60d33de3..9efed053c922 100644 --- a/packages/swingset-liveslots/package.json +++ b/packages/swingset-liveslots/package.json @@ -29,6 +29,7 @@ "@endo/promise-kit": "^0.2.52" }, "peerDependencies": { + "@endo/far": "^0.2.14", "@endo/ses-ava": "^0.2.36", "ava": "^5.1.0" }, diff --git a/packages/swingset-liveslots/src/collectionManager.js b/packages/swingset-liveslots/src/collectionManager.js index 0ae722032530..d6e79a9aae30 100644 --- a/packages/swingset-liveslots/src/collectionManager.js +++ b/packages/swingset-liveslots/src/collectionManager.js @@ -17,7 +17,7 @@ import { makeCopyMap, } from '@agoric/store'; import { Far, passStyleOf } from '@endo/marshal'; -import { parseVatSlot } from './parseVatSlots.js'; +import { makeBaseRef, parseVatSlot } from './parseVatSlots.js'; import { enumerateKeysStartEnd, enumerateKeysWithPrefix, @@ -663,7 +663,7 @@ export function makeCollectionManager( } } - function makeCollection(label, kindName, keyShape, valueShape) { + function makeCollection(label, kindName, isDurable, keyShape, valueShape) { assert.typeof(label, 'string'); assert(storeKindInfo[kindName]); assertKeyPattern(keyShape); @@ -674,7 +674,7 @@ export function makeCollectionManager( } const collectionID = allocateCollectionID(); const kindID = obtainStoreKindID(kindName); - const vobjID = `o+${kindID}/${collectionID}`; + const vobjID = makeBaseRef(kindID, collectionID, isDurable); syscall.vatstoreSet(prefixc(collectionID, '|nextOrdinal'), '1'); const { hasWeakKeys } = storeKindInfo[kindName]; @@ -782,6 +782,7 @@ export function makeCollectionManager( const [vobjID, collection] = makeCollection( label, kindName, + durable, keyShape, valueShape, ); @@ -828,6 +829,7 @@ export function makeCollectionManager( const [vobjID, collection] = makeCollection( label, kindName, + durable, keyShape, valueShape, ); @@ -855,6 +857,7 @@ export function makeCollectionManager( const [vobjID, collection] = makeCollection( label, kindName, + durable, keyShape, valueShape, ); @@ -884,6 +887,7 @@ export function makeCollectionManager( const [vobjID, collection] = makeCollection( label, kindName, + durable, keyShape, valueShape, ); diff --git a/packages/swingset-liveslots/src/liveslots.js b/packages/swingset-liveslots/src/liveslots.js index c6c24cc71ad6..02e3711a2494 100644 --- a/packages/swingset-liveslots/src/liveslots.js +++ b/packages/swingset-liveslots/src/liveslots.js @@ -160,9 +160,9 @@ function build( function retainExportedVref(vref) { // if the vref corresponds to a Remotable, keep a strong reference to it // until the kernel tells us to release it - const { type, allocatedByVat, virtual } = parseVatSlot(vref); + const { type, allocatedByVat, virtual, durable } = parseVatSlot(vref); if (type === 'object' && allocatedByVat) { - if (virtual) { + if (virtual || durable) { // eslint-disable-next-line no-use-before-define vrm.setExportStatus(vref, 'reachable'); } else { @@ -292,9 +292,10 @@ function build( const deadBaseRefs = Array.from(deadSet); deadBaseRefs.sort(); for (const baseRef of deadBaseRefs) { - const { virtual, allocatedByVat, type } = parseVatSlot(baseRef); + const { virtual, durable, allocatedByVat, type } = + parseVatSlot(baseRef); assert(type === 'object', `unprepared to track ${type}`); - if (virtual) { + if (virtual || durable) { // Representative: send nothing, but perform refcount checking // eslint-disable-next-line no-use-before-define const [gcAgain, retirees] = vrm.possibleVirtualObjectDeath(baseRef); @@ -752,11 +753,11 @@ function build( // m.unserialize) must be wrapped by unmetered(). function convertSlotToVal(slot, iface = undefined) { meterControl.assertNotMetered(); - const { type, allocatedByVat, virtual, facet, baseRef } = + const { type, allocatedByVat, virtual, durable, facet, baseRef } = parseVatSlot(slot); let val = getValForSlot(baseRef); if (val) { - if (virtual) { + if (virtual || durable) { if (facet !== undefined) { return val[facet]; } @@ -764,7 +765,7 @@ function build( return val; } let result; - if (virtual) { + if (virtual || durable) { assert.equal(type, 'object'); val = vrm.reanimate(baseRef); if (facet !== undefined) { @@ -1237,8 +1238,8 @@ function build( if (o) { exportedRemotables.delete(o); } - const { virtual } = parseVatSlot(vref); - if (virtual) { + const { virtual, durable } = parseVatSlot(vref); + if (virtual || durable) { vrm.setExportStatus(vref, 'recognizable'); } } @@ -1246,11 +1247,11 @@ function build( function retireOneExport(vref) { insistVatType('object', vref); - const { virtual, allocatedByVat, type } = parseVatSlot(vref); + const { virtual, durable, allocatedByVat, type } = parseVatSlot(vref); assert(allocatedByVat); assert.equal(type, 'object'); // console.log(`-- liveslots acting on retireExports ${vref}`); - if (virtual) { + if (virtual || durable) { vrm.setExportStatus(vref, 'none'); } else { // Remotable @@ -1338,6 +1339,7 @@ function build( ...vrm.testHooks, ...collectionManager.testHooks, setSyscallCapdataLimits, + vatGlobals, }); function setVatOption(option, value) { diff --git a/packages/swingset-liveslots/src/parseVatSlots.js b/packages/swingset-liveslots/src/parseVatSlots.js index 0c1f1e05952e..28863aaaf8db 100644 --- a/packages/swingset-liveslots/src/parseVatSlots.js +++ b/packages/swingset-liveslots/src/parseVatSlots.js @@ -21,15 +21,16 @@ import { assert, Fail } from '@agoric/assert'; * subid: Nat, * baseRef: STRING, * facet: Nat, - * virtual: BOOL, // true=>vref designates a virtual object + * virtual: BOOL, // true=>vref designates a "merely virtual" object (not durable, not ephemeral) + * durable: BOOL, // designates a durable (not merely virtual, not ephemeral) * } * * A vref string can take one of the forms: * * T-N * T+N - * T+N/I - * T+N/I:F + * T+DN/I + * T+DN/I:F * * Where: * @@ -42,6 +43,13 @@ import { assert, Fail } from '@agoric/assert'; * (typically an import) or '+ for the vat (typically an export). This is * returned in the `allocatedByVat` property of the result as a boolean. * + * D is the durability status: for object exports ("o+"), this will + * be the letter 'd' for durable objects, the letter 'v' for + * non-durable ("merely virtual") objects, or empty for + * "Remotable" (ephemeral) objects. It is empty for object + * imports ("o-"), and both both imports and exports of all other + * types (promises, devices) + * * N is a decimal integer representing the identity of the referenced entity. * This is returned in the `id` property of the result as a BigInt. * @@ -79,7 +87,7 @@ import { assert, Fail } from '@agoric/assert'; * XXX TODO: The previous comment suggests some renaming is warranted: * * In the current implementation, a vref string may only include decimal digits, - * the letters 'd', 'o', and 'p', and the punctuation characters '+', '-', '/', + * the letters 'd'/'o'/'p'/'v', and the punctuation characters '+', '-', '/', * and ':'. Future evolution of the vref syntax might add more characters to * this set, but the characters '|' and ',' are permanently excluded: '|' is * used (notably by the collection manager) as delimiter in vatstore keys that @@ -113,7 +121,7 @@ export function parseVatSlot(vref) { let allocatedByVat; const typechar = baseRef[0]; const allocchar = baseRef[1]; - const idSuffix = baseRef.slice(2); + let idSuffix = baseRef.slice(2); if (typechar === 'o') { type = 'object'; @@ -133,14 +141,22 @@ export function parseVatSlot(vref) { Fail`invalid vref ${vref}`; } + let virtual = false; + let durable = false; + if (idSuffix.startsWith('d')) { + durable = true; + idSuffix = idSuffix.slice(1); + } else if (idSuffix.startsWith('v')) { + virtual = true; // merely virtual + idSuffix = idSuffix.slice(1); + } const delim = idSuffix.indexOf('/'); let id; let subid; let facet; - let virtual = false; if (delim > 0) { (type === 'object' && allocatedByVat) || Fail`invalid vref ${vref}`; - virtual = true; + virtual || durable || Fail`invalid vref ${vref}`; // subid only exists for virtuals/durables id = Nat(BigInt(idSuffix.substr(0, delim))); subid = Nat(BigInt(idSuffix.slice(delim + 1))); } else { @@ -150,7 +166,7 @@ export function parseVatSlot(vref) { facet = Nat(BigInt(facetStr)); } - return { type, allocatedByVat, virtual, id, subid, baseRef, facet }; + return { type, allocatedByVat, virtual, durable, id, subid, baseRef, facet }; } /** @@ -184,6 +200,10 @@ export function makeVatSlot(type, allocatedByVat, id) { throw Fail`unknown type ${type}`; } +export function makeBaseRef(kindID, id, isDurable) { + return `o+${isDurable ? 'd' : 'v'}${kindID}/${id}`; +} + /** * Assert function to ensure that a vat slot reference string refers to a * slot of a given type. diff --git a/packages/swingset-liveslots/src/stop-vat.js b/packages/swingset-liveslots/src/stop-vat.js index 8c9a0298f207..488fe2af9207 100644 --- a/packages/swingset-liveslots/src/stop-vat.js +++ b/packages/swingset-liveslots/src/stop-vat.js @@ -91,7 +91,10 @@ function identifyExportedFacets(vrefSet, { syscall, vrm }) { const value = syscall.vatstoreGet(key); const baseRef = key.slice(prefix.length); const parsed = parseVatSlot(baseRef); - assert(parsed.virtual && parsed.baseRef === baseRef, baseRef); + assert( + (parsed.virtual || parsed.durable) && parsed.baseRef === baseRef, + baseRef, + ); if (!vrm.isDurableKind(parsed.id)) { if (value.length === 1) { // single-facet @@ -208,7 +211,7 @@ function deleteVirtualObjectsWithDecref({ syscall, vrm }) { for (const capdata of Object.values(raw)) { for (const vref of capdata.slots) { const p2 = parseVatSlot(vref); - if (p2.virtual && vrm.isDurableKind(p2.id)) { + if ((p2.virtual || p2.durable) && vrm.isDurableKind(p2.id)) { const count = durableDecrefs.get(p2.baseRef) || 0; durableDecrefs.set(p2.baseRef, count + 1); } @@ -268,7 +271,7 @@ function deleteCollectionsWithDecref({ syscall, vrm }) { if (!isDurable && !isMeta) { for (const vref of JSON.parse(value).slots) { const p = parseVatSlot(vref); - if (p.virtual && vrm.isDurableKind(p.id)) { + if ((p.virtual || p.durable) && vrm.isDurableKind(p.id)) { const count = durableDecrefs.get(p.baseRef) || 0; durableDecrefs.set(p.baseRef, count + 1); } diff --git a/packages/swingset-liveslots/src/vatstore-usage.md b/packages/swingset-liveslots/src/vatstore-usage.md index 43cf9cf0848d..70cad7386ec7 100644 --- a/packages/swingset-liveslots/src/vatstore-usage.md +++ b/packages/swingset-liveslots/src/vatstore-usage.md @@ -15,6 +15,11 @@ The rest of the vat's keyspace is used by liveslots: This file describes the layout of the vatstore keyspace. +# vref virtual/durable annotations + +When vats export object references into the kernel, they create "vrefs" as reference identifiers. For non-virtual objects (ephemeral "Remotables", held in RAM rather than the DB), these take the form of `o+NN` (e.g. `o+0`, `o+12`, `o+937`). Virtual and durable exported objects get an additional `v` or `d` annotation, so their vrefs start with e.g. `o+v99` or `o+v132` for virtual objects, and `o+d3` or `o+d77` for durable objects. The kernel can use these annotations to assist in the cleanup process when a vat is upgraded, and non-durable objects are abandoned. + +Kind handles are necessarily durable, as is the "baggage" object. Promises are necessarily ephemeral, so vat-allocated promise vrefs (aka "vpids") always use the `p+NN` form, and never have a `v` or `d` annotation. # Counters @@ -27,9 +32,10 @@ Liveslots maintains three durable counters to create the distinct vrefs that it The current version of liveslots consumes the first ten exportIDs (covering `o+0` through `o+9`) during vat startup, leaving `10` as the first Kind ID available for userspace. * `o+0`: root object -* `o+1`: identifies the KindHandle Kind ID, each `o+1/${kindHandleID}` instance of which is a KindHandle -* `o+2` through `o+9`: KindHandles for the built-in [virtual/durable collections](#virtualdurable-collections-aka-stores) -* `o+10`: first available for userspace Remotables or Kinds +* `o+d1`: identifies the KindHandle Kind ID, each `o+d1/${kindHandleID}` instance of which is a KindHandle +* `o+v2` through `o+v5`: KindHandles for the built-in [virtual collections](#virtualdurable-collections-aka-stores) +* `o+d6` through `o+d9`: KindHandles for the built-in [durable collections](#virtualdurable-collections-aka-stores) +* `o+10` / `o+v10` / `o+d10`: first available for userspace Remotables or Kinds # Virtual Object Kinds @@ -44,10 +50,10 @@ Each time the kind constructor is called, a new "baseref" is allocated for the c Those vrefs are used when interacting with the kernel (in `syscall.send` etc.), and in virtualized data (inside the capdata `slots` that point to other objects), but the vatstore keys that track GC refcounts and export status use the "baseref" instead. The baseref is built out of two pieces separated by a literal `/`: -1. Prefixed Kind ID, e.g. `o+11`. These are allocated from the same "Export ID" numberspace as exported Remotables (JavaScript objects marked with `Far`). +1. Prefixed Kind ID, e.g. `o+v11` or `o+d11`. These are allocated from the same "Export ID" numberspace as exported Remotables (JavaScript objects marked with `Far`). 2. Instance ID, an integer. `1` for the first instance of each Kind and incremented for each subsequent instance. -The vref for a Representative of a single-facet Kind is just the `o+${kindID}/${instanceID}` baseref. The vref for a Representative of a facet of a multi-facet Kind extends that to `o+${kindID}/${instanceID}:${facetID}`, where the additional component is: +The vref for a Representative of a single-facet virtual Kind is just the `o+v${kindID}/${instanceID}` baseref. The vref for a Representative of a facet of a multi-facet virtual Kind extends that to `o+v${kindID}/${instanceID}:${facetID}`, where the additional component is: 3. Facet ID, an integer. `0` for the first facet and incremented for each subsequent facet. In a c-list or virtualized data, you may see vrefs like these: @@ -55,23 +61,23 @@ In a c-list or virtualized data, you may see vrefs like these: * `o-3`: an imported Presence, pointing to some object in a different vat * `o+0`: the root object, a plain Remotable * `o+10`: another plain Remotable, exported from this vat or stored in virtualized data -* `o+11/1`: a Representative for the first instance of single-facet Kind "o+11" -* `o+11/2`: a Representative for the second instance of single-facet Kind "o+11" -* `o+12/1:0`: the first facet of the first instance of a multi-facet Kind "o+12" -* `o+12/1:1`: the second facet of that same instance -* `o+12/2:0`: the first facet of a different instance -* `o+12/3:0`: the first facet of another different instance +* `o+d11/1`: a Representative for the first instance of single-facet virtual Kind "o+v11" +* `o+d11/2`: a Representative for the second instance of single-facet virtual Kind "o+v11" +* `o+d12/1:0`: the first facet of the first instance of a multi-facet durable Kind "o+d12" +* `o+d12/1:1`: the second facet of that same instance +* `o+d12/2:0`: the first facet of a different instance +* `o+d12/3:0`: the first facet of another different instance -Each instance of a virtual object stores state in a vatstore key indexed by the baseref. If `o+12/1:0` and `o+12/1:1` are the facet vrefs for a cohort whose baseref is `o+12/1`, the cohort's shared state will be stored in `vom.o+12/1` as a JSON-serialized record. The keys of this record are property names: if the Kind uses `state.prop1`, the record will have a key named `prop1`. For each property, the value is a `{ body: string, slots: any[] }` capdata record with string-valued slot items (and unlike the treatment of capdata in many other parts of the system, it is not independently serialized). +Each instance of a virtual object stores state in a vatstore key indexed by the baseref. If `o+d12/1:0` and `o+d12/1:1` are the facet vrefs for a cohort whose baseref is `o+d12/1`, the cohort's shared state will be stored in `vom.o+d12/1` as a JSON-serialized record. The keys of this record are property names: if the Kind uses `state.prop1`, the record will have a key named `prop1`. For each property, the value is a `{ body: string, slots: any[] }` capdata record with string-valued slot items (and unlike the treatment of capdata in many other parts of the system, it is not independently serialized). -* `v6.vs.vom.o+12/1` : `{"booleanProp":{"body":"true","slots":[]},"arrayProp":{"body":"[]","slots":[]}}` +* `v6.vs.vom.o+d12/1` : `{"booleanProp":{"body":"true","slots":[]},"arrayProp":{"body":"[]","slots":[]}}` In the refcounting portion of the vatstore (`vom.rc.${baseref}`), you will see baserefs: * `v6.vs.vom.rc.o+10`: the count of virtualized references to plain Remotable `o+10` (held in RAM) -* `v6.vs.vom.rc.o+12/1`: the count of references to any member of the cohort for the first instance of Kind `o+12` +* `v6.vs.vom.rc.o+d12/1`: the count of references to any member of the cohort for the first instance of Kind `o+d12` * This Kind might be single-facet or multi-facet. - * References to distinct facets of the same cohort are counted independently. For example, if one object references both the first and second facets (`o+12/1:0` and `o+12/1:1`), it accounts for two increments to this value. + * References to distinct facets of the same cohort are counted independently. For example, if one object references both the first and second facets (`o+d12/1:0` and `o+d12/1:1`), it accounts for two increments to this value. In the export-status portion of the vatstore (`vom.es.${baseref}`), you will see baserefs, and any facets are tracked in the value, not the key: @@ -79,9 +85,9 @@ In the export-status portion of the vatstore (`vom.es.${baseref}`), you will see * value `r`: the plain Remotable has been exported and is "reachable" by the kernel * value `s`: the Remotable was exported, the kernel dropped it, and is still "recognizable" by the kernel ("s" for "see") * If the kernel can neither reach nor recognize the export, the vatstore key will be missing entirely. -* `v6.vs.vom.es.o+12/1` records the export status for all facets of virtual object `o+12/1` +* `v6.vs.vom.es.o+d12/1` records the export status for all facets of virtual object `o+d12/1` * If the Kind is single-facet, the value will be the same as for a plain Remotable: a single `r` or `s` character - * If the Kind is multi-facet, the value will be a string with one letter for each facet, in the same order as their Facet ID. `n` is used to indicate neither reachable nor recognizable. For example, a value of `rsnr` means there are four facets, the first (`o+12/1:0`) and last (`o+12/1:3`) are reachable, the second (`o+12/1:1`) is recognizable, and the third (`o+12/1:2`) is neither. + * If the Kind is multi-facet, the value will be a string with one letter for each facet, in the same order as their Facet ID. `n` is used to indicate neither reachable nor recognizable. For example, a value of `rsnr` means there are four facets, the first (`o+d12/1:0`) and last (`o+d12/1:3`) are reachable, the second (`o+d12/1:1`) is recognizable, and the third (`o+d12/1:2`) is neither. # Durable Kinds @@ -90,7 +96,7 @@ Virtual objects are held on disk, which makes them suitable for high-cardinality Durable Kinds are defined just like virtual Kinds, but they use a different constructor (`defineDurableKind` instead of `defineKind`), which requires a "handle" created by `makeKindHandle`. Durable virtual objects can only hold durable data in their `state`. -The KindHandle is a durable virtual object of a special internal Kind. This is the first Kind allocated, so usually it gets Kind ID 1 and the handles get vrefs like `o+1/${kindHandleID}`. +The KindHandle is a durable virtual object of a special internal Kind. This is the first Kind allocated, so usually it gets Kind ID 1 and the handles get vrefs like `o+d1/${kindHandleID}`. # Kind Metadata @@ -123,19 +129,19 @@ These index values are stored in `storeKindIDTable`, as a mapping from the colle * `v6.vs.storeKindIDTable` : `{"scalarMapStore":2,"scalarWeakMapStore":3,"scalarSetStore":4,"scalarWeakSetStore":5,"scalarDurableMapStore":6,"scalarDurableWeakMapStore":7,"scalarDurableSetStore":8,"scalarDurableWeakSetStore":9}` -which means `2` is the Kind ID for non-durable merely-virtual "scalarMapStore", instances of which will have vrefs like `o+2/${collectionID}`. +which means `2` is the Kind ID for non-durable merely-virtual "scalarMapStore", instances of which will have vrefs like `o+v2/${collectionID}`. -Each new store, regardless of type, is allocated the next available Collection ID. This is an incrementing integer that starts at 1, and is independent of the "Export ID" numberspace used by exported Remotables and Kind IDs. The same Collection ID numberspace is shared by all collection types. So unlike virtual objects (where the instanceID in `o+${kindID}/${instanceID}` is scoped to `o+${kindID}`), for collections the collectionID in `o+${collectionType}/${collectionID}` is global to the entire vat. No two stores will have the same collectionID, even if they are of different types. +Each new store, regardless of type, is allocated the next available Collection ID. This is an incrementing integer that starts at 1, and is independent of the "Export ID" numberspace used by exported Remotables and Kind IDs. The same Collection ID numberspace is shared by all collection types. So unlike virtual objects (where the instanceID in `o+v${kindID}/${instanceID}` is scoped to `o+v${kindID}`), for collections the collectionID in `o+v${collectionType}/${collectionID}` is global to the entire vat. No two stores will have the same collectionID, even if they are of different types. -The interpretation of a vref therefore varies based on whether the initial "type" portion before a slash (`o+${exportID}`) identifies a collection type or a virtual object kind: +The interpretation of a vref therefore varies based on whether the initial "type" portion before a slash (`o+v${exportID}` or `o+d${exportID}`) identifies a collection type or a virtual object kind: -* `o+6/1`: `6` is a collection type in the storeKindIDTable ("scalarDurableMapStore"), so `o+6/1` refers to the first collection (of any type) in the vat -* `o+7/1`: `7` is also a collection type in the storeKindIDTable ("scalarDurableWeakMapStore"), so `o+7/1` is guaranteed not to coexist with `o+6/1` -* `o+7/2`: refers to the second collection (of any type) in the vat, which has type `7` ("scalarDurableWeakMapStore") -* `o+5/3`: refers to the third collection (of any type) in the vat, which has type `5` ("scalarWeakSetStore") -* `o+5/4`: refers to the fourth collection (of any type) in the vat, also a scalarWeakSetStore -* `o+11/1`: `11` is not a collection type in the storeKindIDTable, so is instead a kind and `o+11/1` refers to the first instance of that kind -* `o+11/2`: refers to the second instance of that kind +* `o+d6/1`: `6` is a collection type in the storeKindIDTable ("scalarDurableMapStore"), so `o+d6/1` refers to the first collection (of any type) in the vat +* `o+d7/1`: `7` is also a collection type in the storeKindIDTable ("scalarDurableWeakMapStore"), so `o+d7/1` is guaranteed not to coexist with `o+d6/1` +* `o+d7/2`: refers to the second collection (of any type) in the vat, which has type `7` ("scalarDurableWeakMapStore") +* `o+v5/3`: refers to the third collection (of any type) in the vat, which has type `5` ("scalarWeakSetStore") +* `o+v5/4`: refers to the fourth collection (of any type) in the vat, also a scalarWeakSetStore +* `o+v11/1`: `11` is not a collection type in the storeKindIDTable, so is instead a kind and `o+v11/1` refers to the first instance of that kind +* `o+v11/2`: refers to the second instance of that kind # Baggage @@ -144,9 +150,9 @@ Most collections are created by userspace code, but to support vat upgrade, live This object needs to be pre-generated because the second (and subsequent) versions of the vat will use it to reach all other durable objects from their predecessors, so v2 can remember things that were stored by v1. The most significant values of "baggage" are the KindHandles for durable Kinds made by v1. V2 will need these to call `defineDurableKind` and re-attach behavior for each one. Each version must re-attach behavior for *all* durable Kinds created by its predecessors. -`o+6/1` is allocated for the "baggage" collection, indicating that it is a scalarDurableMapStore (`o+6` is used for that collection type), and also that it is the first collection (of any type) allocated in the vat. +`o+d6/1` is allocated for the "baggage" collection, indicating that it is a scalarDurableMapStore (`o+d6` is used for that collection type), and also that it is the first collection (of any type) allocated in the vat. -If userspace version 1 starts `buildRootObject` by calling `makeScalarBigWeakSetStore()` and then three `makeScalarSetStore()`s, they are likely to be assigned `o+5/2`, `o+4/3`, `o+4/4`, and `o+4/5` respectively. Such collections IDs start with `2` because `1` is claimed by baggage. +If userspace version 1 starts `buildRootObject` by calling `makeScalarBigWeakSetStore()` and then three `makeScalarSetStore()`s, they are likely to be assigned `o+v5/2`, `o+v4/3`, `o+v4/4`, and `o+v4/5` respectively. Such collections IDs start with `2` because `1` is claimed by baggage. # Collection Data Records @@ -179,10 +185,10 @@ The `schemata` is a capdata serialization of the Matcher constraints recorded fo Each entry in the collection gets put into a single vatstore entry: -* `v6.vs.vc.2.skey1`: `{"body":"{\"@qclass\":\"slot\",\"iface\":\"Alleged: foo\",\"index\":0}","slots":["o+9/1"]}` -* `v6.vs.vc.2.skey2`: `{"body":"{\"@qclass\":\"slot\",\"iface\":\"Alleged: foo\",\"index\":0}","slots":["o+9/1"]}` -* `v6.vs.vc.2.skey3`: `{"body":"{\"@qclass\":\"slot\",\"iface\":\"Alleged: foo\",\"index\":0}","slots":["o+9/1"]}` -* `v6.vs.vc.2.skey4`: `{"body":"{\"@qclass\":\"slot\",\"iface\":\"Alleged: foo\",\"index\":0}","slots":["o+9/2"]}` +* `v6.vs.vc.2.skey1`: `{"body":"{\"@qclass\":\"slot\",\"iface\":\"Alleged: foo\",\"index\":0}","slots":["o+d9/1"]}` +* `v6.vs.vc.2.skey2`: `{"body":"{\"@qclass\":\"slot\",\"iface\":\"Alleged: foo\",\"index\":0}","slots":["o+d9/1"]}` +* `v6.vs.vc.2.skey3`: `{"body":"{\"@qclass\":\"slot\",\"iface\":\"Alleged: foo\",\"index\":0}","slots":["o+d9/1"]}` +* `v6.vs.vc.2.skey4`: `{"body":"{\"@qclass\":\"slot\",\"iface\":\"Alleged: foo\",\"index\":0}","slots":["o+d9/2"]}` The key string for each entry (e.g. `skey1`) is formed by serializing the key object. Strings get a simple `s` prefix. Other objects use more complex encodings, designed to allow numbers (floats and BigInts, separately) to sort numerically despite the kvStore keys sorting lexicographically. See `packages/store/src/patterns/encodePassable.js` for details. Object references involve an additional kvStore entry, to manage the mapping from Object to ordinal and back. diff --git a/packages/swingset-liveslots/src/virtualObjectManager.js b/packages/swingset-liveslots/src/virtualObjectManager.js index 8e5c44aed69c..4f28d8de8966 100644 --- a/packages/swingset-liveslots/src/virtualObjectManager.js +++ b/packages/swingset-liveslots/src/virtualObjectManager.js @@ -5,7 +5,7 @@ import { objectMap } from '@agoric/internal'; import { assertPattern, mustMatch } from '@agoric/store'; import { defendPrototype, defendPrototypeKit } from '@agoric/store/tools.js'; import { Far, hasOwnPropertyOf, passStyleOf } from '@endo/marshal'; -import { parseVatSlot } from './parseVatSlots.js'; +import { parseVatSlot, makeBaseRef } from './parseVatSlots.js'; import { enumerateKeysWithPrefix } from './vatstore-iterators.js'; /** @template T @typedef {import('@agoric/vat-data').DefineKindOptions} DefineKindOptions */ @@ -815,7 +815,7 @@ export function makeVirtualObjectManager( function makeNewInstance(...args) { const id = getNextInstanceID(kindID, isDurable); - const baseRef = `o+${kindID}/${id}`; + const baseRef = makeBaseRef(kindID, id, isDurable); // kdebug(`vo make ${baseRef}`); const initialData = init ? init(...args) : {}; @@ -949,7 +949,7 @@ export function makeVirtualObjectManager( const makeKindHandle = tag => { assert(kindIDID, `initializeKindHandleKind not called yet`); const kindID = `${allocateExportID()}`; - const kindIDvref = `o+${kindIDID}/${kindID}`; + const kindIDvref = makeBaseRef(kindIDID, kindID, true); const durableKindDescriptor = { kindID, tag, nextInstanceID: 1 }; /** @type {import('@agoric/vat-data').DurableKindHandle} */ // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- https://github.com/Agoric/agoric-sdk/issues/4620 diff --git a/packages/swingset-liveslots/src/virtualReferences.js b/packages/swingset-liveslots/src/virtualReferences.js index 8aa25aa6abee..b1d3846ae56e 100644 --- a/packages/swingset-liveslots/src/virtualReferences.js +++ b/packages/swingset-liveslots/src/virtualReferences.js @@ -257,7 +257,7 @@ export function makeVirtualReferenceManager( * @returns {boolean} true if the indicated object reference is durable. */ function isDurable(vref) { - const { type, id, virtual, allocatedByVat } = parseVatSlot(vref); + const { type, id, virtual, durable, allocatedByVat } = parseVatSlot(vref); if (relaxDurabilityRules) { // we'll pretend an object is durable if running with relaxed rules return true; @@ -270,7 +270,7 @@ export function makeVirtualReferenceManager( } else if (!allocatedByVat) { // imports are durable return true; - } else if (virtual) { + } else if (virtual || durable) { // stores and virtual objects are durable if their kinds are so configured return isDurableKind(id); } else { @@ -336,10 +336,11 @@ export function makeVirtualReferenceManager( // from the `remotableRefCounts` map). function addReachableVref(vref) { - const { type, virtual, allocatedByVat, baseRef } = parseVatSlot(vref); + const { type, virtual, durable, allocatedByVat, baseRef } = + parseVatSlot(vref); if (type === 'object') { if (allocatedByVat) { - if (virtual) { + if (virtual || durable) { incRefCount(baseRef); } else { // exported non-virtual object: Remotable @@ -362,10 +363,11 @@ export function makeVirtualReferenceManager( function removeReachableVref(vref) { let droppedMemoryReference = false; - const { type, virtual, allocatedByVat, baseRef } = parseVatSlot(vref); + const { type, virtual, durable, allocatedByVat, baseRef } = + parseVatSlot(vref); if (type === 'object') { if (allocatedByVat) { - if (virtual) { + if (virtual || durable) { decRefCount(baseRef); } else { // exported non-virtual object: Remotable @@ -402,10 +404,11 @@ export function makeVirtualReferenceManager( // for testing only function getReachableRefCount(vref) { - const { type, virtual, allocatedByVat, baseRef } = parseVatSlot(vref); + const { type, virtual, durable, allocatedByVat, baseRef } = + parseVatSlot(vref); assert(type === 'object'); if (allocatedByVat) { - if (virtual) { + if (virtual || durable) { return getRefCount(baseRef); } else { const remotable = requiredValForSlot(baseRef); @@ -499,8 +502,8 @@ export function makeVirtualReferenceManager( function addRecognizableValue(value, recognizer, recognizerIsVirtual) { const vref = getSlotForVal(value); if (vref) { - const { type, allocatedByVat, virtual } = parseVatSlot(vref); - if (type === 'object' && (!allocatedByVat || virtual)) { + const { type, allocatedByVat, virtual, durable } = parseVatSlot(vref); + if (type === 'object' && (!allocatedByVat || virtual || durable)) { if (recognizerIsVirtual) { syscall.vatstoreSet(`vom.ir.${vref}|${recognizer}`, '1'); } else { @@ -516,8 +519,8 @@ export function makeVirtualReferenceManager( } function removeRecognizableVref(vref, recognizer, recognizerIsVirtual) { - const { type, allocatedByVat, virtual } = parseVatSlot(vref); - if (type === 'object' && (!allocatedByVat || virtual)) { + const { type, allocatedByVat, virtual, durable } = parseVatSlot(vref); + if (type === 'object' && (!allocatedByVat || virtual || durable)) { if (recognizerIsVirtual) { syscall.vatstoreDelete(`vom.ir.${vref}|${recognizer}`); } else { @@ -557,7 +560,7 @@ export function makeVirtualReferenceManager( function ceaseRecognition(vref) { let doMoreGC = false; const p = parseVatSlot(vref); - if (p.allocatedByVat && p.virtual && p.facet === undefined) { + if (p.allocatedByVat && (p.virtual || p.durable) && p.facet === undefined) { // If `vref` identifies a multi-faceted object that should no longer be // "recognized", what that really means that all references to its // individual facets should no longer be recognized -- nobody actually @@ -621,8 +624,8 @@ export function makeVirtualReferenceManager( function vrefKey(value) { const vobjID = getSlotForVal(value); if (vobjID) { - const { type, virtual, allocatedByVat } = parseVatSlot(vobjID); - if (type === 'object' && (virtual || !allocatedByVat)) { + const { type, virtual, durable, allocatedByVat } = parseVatSlot(vobjID); + if (type === 'object' && (virtual || durable || !allocatedByVat)) { return vobjID; } } diff --git a/packages/swingset-liveslots/src/watchedPromises.js b/packages/swingset-liveslots/src/watchedPromises.js index e7272be96f83..8a7e6cabbc7e 100644 --- a/packages/swingset-liveslots/src/watchedPromises.js +++ b/packages/swingset-liveslots/src/watchedPromises.js @@ -180,8 +180,8 @@ export function makeWatchedPromiseManager( void Promise.resolve().then(() => { const watcherVref = convertValToSlot(watcher); assert(watcherVref, 'invalid watcher'); - const { virtual } = parseVatSlot(watcherVref); - assert(virtual, 'promise watcher must be a virtual object'); + const { virtual, durable } = parseVatSlot(watcherVref); + assert(virtual || durable, 'promise watcher must be a virtual object'); if (watcher.onFulfilled) { assert.typeof(watcher.onFulfilled, 'function'); } diff --git a/packages/swingset-liveslots/test/gc-helpers.js b/packages/swingset-liveslots/test/gc-helpers.js index bbf8dd9307ba..b85012531edd 100644 --- a/packages/swingset-liveslots/test/gc-helpers.js +++ b/packages/swingset-liveslots/test/gc-helpers.js @@ -140,7 +140,7 @@ export function deduceCollectionID(fakestore, ctype, offset) { const kindID = kindIDs[ctype]; assert(kindID); const collectionID = nextCollectionID - offset; - const vref = `o+${kindID}/${collectionID}`; + const vref = `o+v${kindID}/${collectionID}`; return [collectionID, vref]; } @@ -153,7 +153,7 @@ export function refValString(vref, type) { } export function mapRef(idx) { - return `o+2/${idx}`; // see 'assert known scalarMapStore ID' below + return `o+v2/${idx}`; // see 'assert known scalarMapStore ID' below } // return an iterator of all existing keys that start with 'prefix' diff --git a/packages/swingset-liveslots/test/liveslots-helpers.js b/packages/swingset-liveslots/test/liveslots-helpers.js index bb4006c89752..40904d9c83a5 100644 --- a/packages/swingset-liveslots/test/liveslots-helpers.js +++ b/packages/swingset-liveslots/test/liveslots-helpers.js @@ -129,7 +129,6 @@ export async function makeDispatch( build, vatID = 'vatA', liveSlotsOptions = {}, - returnTestHooks, ) { const gcTools = harden({ WeakRef, @@ -150,10 +149,7 @@ export async function makeDispatch( }, ); await dispatch(['startVat', kser()]); - if (returnTestHooks) { - returnTestHooks[0] = testHooks; - } - return dispatch; + return { dispatch, testHooks }; } function makeRPMaker() { @@ -173,15 +169,12 @@ export async function setupTestLiveslots( ) { const { log, syscall, fakestore } = buildSyscall(skipLogging); const nextRP = makeRPMaker(); - const th = []; - const dispatch = await makeDispatch( + const { dispatch, testHooks } = await makeDispatch( syscall, buildRootObject, vatName, { virtualObjectCacheSize: 0 }, - th, ); - const [testHooks] = th; async function dispatchMessage(message, ...args) { const rp = nextRP(); diff --git a/packages/swingset-liveslots/test/storeGC/test-lifecycle.js b/packages/swingset-liveslots/test/storeGC/test-lifecycle.js index 74087d82c5da..e7de7cef5dac 100644 --- a/packages/swingset-liveslots/test/storeGC/test-lifecycle.js +++ b/packages/swingset-liveslots/test/storeGC/test-lifecycle.js @@ -16,7 +16,7 @@ function getLastCollection(v) { // makeAndHold() uses makeScalarBigMapStore, and since we call it // early, it always gets "store #6", in vref o+2/6 (o+2 means // scalarMapStore, non-durable, and /6 means collectionID=6) - const vref = 'o+2/6'; + const vref = 'o+v2/6'; // double-check that at least the collectionID is right const { t, fakestore } = v; t.is(JSON.parse(fakestore.get('idCounters')).collectionID, 7); // last was 6 diff --git a/packages/swingset-liveslots/test/storeGC/test-scalar-store-kind.js b/packages/swingset-liveslots/test/storeGC/test-scalar-store-kind.js index 4751a8b0fe45..a34025913e39 100644 --- a/packages/swingset-liveslots/test/storeGC/test-scalar-store-kind.js +++ b/packages/swingset-liveslots/test/storeGC/test-scalar-store-kind.js @@ -21,5 +21,5 @@ test.serial('assert known scalarMapStore ID', async t => { const { testHooks } = await setupTestLiveslots(t, buildRootObject, 'bob', true); const id = testHooks.obtainStoreKindID('scalarMapStore'); t.is(id, 2); - t.is(mapRef('INDEX'), 'o+2/INDEX'); + t.is(mapRef('INDEX'), 'o+v2/INDEX'); }); diff --git a/packages/swingset-liveslots/test/test-baggage.js b/packages/swingset-liveslots/test/test-baggage.js index de113faacef2..61f44028fc76 100644 --- a/packages/swingset-liveslots/test/test-baggage.js +++ b/packages/swingset-liveslots/test/test-baggage.js @@ -32,7 +32,7 @@ test.serial('exercise baggage', async t => { console.log(`baggageID`, baggageID); const kindIDs = JSON.parse(fakestore.get('storeKindIDTable')); // baggage is the first collection created, a scalarDurableMapStore - t.is(baggageVref, `o+${kindIDs.scalarDurableMapStore}/1`); + t.is(baggageVref, `o+d${kindIDs.scalarDurableMapStore}/1`); t.is(fakestore.get(`vc.${baggageID}.|label`), 'baggage'); const outsideVal = fakestore.get(`vc.${baggageID}.soutside`); t.is(outsideVal, vstr('outer val')); diff --git a/packages/swingset-liveslots/test/test-initial-vrefs.js b/packages/swingset-liveslots/test/test-initial-vrefs.js new file mode 100644 index 000000000000..64ed70318478 --- /dev/null +++ b/packages/swingset-liveslots/test/test-initial-vrefs.js @@ -0,0 +1,142 @@ +import test from 'ava'; +import '@endo/init/debug.js'; + +import { Far } from '@endo/far'; +import { M } from '@agoric/store'; +import { setupTestLiveslots } from './liveslots-helpers.js'; +import { kunser } from './kmarshal.js'; + +function buildRootObject(vatPowers, vatParameters, baggage) { + const vd = vatPowers.VatData; + + let vinstance1; + let kh1; + let dinstance1; + let dinstance2; + let store1; + + function start() { + // this is the first Kind to be created, so it gets the first + // objectID (probably 10), and a 'v' annotation, so o+v10 + const init = () => ({ state: 0 }); + const behavior = { + set: ({ state }, value) => { + state.value = value; + }, + }; + const makeVK1 = vd.defineKind('kind', init, behavior); + // the first instance thus gets o+v10/1 + vinstance1 = makeVK1(); + + // Kind handles are durable instances of the kindIDID + kh1 = vd.makeKindHandle('k1'); + const makeK1 = vd.defineDurableKind(kh1, () => ({}), {}); + baggage.init('kh1', kh1); + dinstance1 = makeK1(); + dinstance2 = makeK1(); + + store1 = vd.makeScalarBigMapStore('store1'); + store1.init('key', 'value'); + store1.init('self', store1); + } + + return Far('root', { + start, + getVinstance1: () => vinstance1, + getKH1: () => kh1, + getDinstance1: () => dinstance1, + getDinstance2: () => dinstance2, + getStore1: () => store1, + }); +} + +test('initial vatstore contents', async t => { + const { v } = await setupTestLiveslots(t, buildRootObject, 'bob', true); + const { fakestore } = v; + const get = key => fakestore.get(key); + + // an empty buildRootObject should create 4 collections, and some metadata + const kindIDs = JSON.parse(get('storeKindIDTable')); + const dmsBase = `o+d${kindIDs.scalarDurableMapStore}`; + // the first collection is baggage: a durable mapstore + const baggageVref = `${dmsBase}/1`; + t.is(get('baggageID'), baggageVref); + t.is(get(`vc.1.|label`), 'baggage'); + t.is(get(`vc.1.|entryCount`), '0'); // no entries yet + t.is(get(`vc.1.|nextOrdinal`), '1'); // no ordinals yet + t.is(get(`vc.1.|entryCount`), '0'); + const stringSchema = [M.string()]; + t.deepEqual(kunser(JSON.parse(get(`vc.1.|schemata`))), stringSchema); + + // then three tables for the promise watcher (one virtual, two durable) + t.is(get(`vc.3.|label`), `promiseWatcherByKind`); // durable + t.is(get(`vc.4.|label`), `watchedPromises`); // durable + // the promiseRegistrations table is not durable, and only gets vc.2 + // on the first incarnation: it will get a new ID on subsequent + // incarnations + t.is(get(`vc.2.|label`), `promiseRegistrations`); // virtual + + const watcherTableVref = get('watcherTableID'); + const watchedPromiseTableVref = get('watchedPromiseTableID'); + t.is(watcherTableVref, `${dmsBase}/3`); + t.is(watchedPromiseTableVref, `${dmsBase}/4`); + + // baggage and the two durable promise-watcher tables are pinned + t.is(get(`vom.rc.${baggageVref}`), '1'); + t.is(get(`vom.rc.${watcherTableVref}`), '1'); + t.is(get(`vom.rc.${watchedPromiseTableVref}`), '1'); + + // promiseRegistrations and promiseWatcherByKind arbitrary scalars as keys + const scalarSchema = [M.scalar()]; + t.deepEqual(kunser(JSON.parse(get(`vc.2.|schemata`))), scalarSchema); + t.deepEqual(kunser(JSON.parse(get(`vc.3.|schemata`))), scalarSchema); + // watchedPromises uses vref (string) keys + t.deepEqual(kunser(JSON.parse(get(`vc.4.|schemata`))), stringSchema); +}); + +test('vrefs', async t => { + const { v, dispatchMessageSuccessfully: run } = await setupTestLiveslots( + t, + buildRootObject, + 'bob', + true, + ); + // const { fakestore, dumpFakestore } = v; + const { fakestore } = v; + const kindIDID = JSON.parse(fakestore.get('kindIDID')); + const initialKindIDs = JSON.parse(fakestore.get('storeKindIDTable')); + const initialCounters = JSON.parse(fakestore.get(`idCounters`)); + const firstObjID = initialCounters.exportID; + + // we expect makeVK1 (virtual, non-durable) to get firstObjID + const expectedVI1Vref = `o+v${firstObjID}/1`; // usually o+v10/1 + // and kh1 is a KindHandle, so its subid is allocated from the objectID space + const kh1KindID = `${firstObjID + 1}`; + const expectedKH1Vref = `o+d${kindIDID}/${kh1KindID}`; // usually o+d1/11 + console.log(`expectedKH1Vref`, expectedKH1Vref); + // instances of kh1 are allocated from the `o+d` kh1KindID space + const expectedDH1Vref = `o+d${kh1KindID}/1`; + const expectedDH2Vref = `o+d${kh1KindID}/2`; + + await run('start'); + // dumpFakestore(); + + const vI1Vref = (await run('getVinstance1')).slots[0]; + t.is(vI1Vref, expectedVI1Vref); + + const kh1Vref = (await run('getKH1')).slots[0]; + t.is(kh1Vref, expectedKH1Vref); + + const dh1Vref = (await run('getDinstance1')).slots[0]; + t.is(dh1Vref, expectedDH1Vref); + const dh2Vref = (await run('getDinstance2')).slots[0]; + t.is(dh2Vref, expectedDH2Vref); + + // the liveslots-created collections consume vc.1 through vc.4, + // leaving vc.5 for the first user-created collection + t.is(fakestore.get('vc.5.|label'), 'store1'); + const expectedStore1Vref = `o+v${initialKindIDs.scalarMapStore}/5`; + const store1Vref = (await run('getStore1')).slots[0]; + t.is(store1Vref, expectedStore1Vref); + t.deepEqual(kunser(JSON.parse(fakestore.get(`vc.5.s${'key'}`))), 'value'); +}); diff --git a/packages/swingset-liveslots/test/test-liveslots.js b/packages/swingset-liveslots/test/test-liveslots.js index 13b0ea077829..a03d26498259 100644 --- a/packages/swingset-liveslots/test/test-liveslots.js +++ b/packages/swingset-liveslots/test/test-liveslots.js @@ -51,7 +51,7 @@ test('calls', async t => { }, }); } - const dispatch = await makeDispatch(syscall, build); + const { dispatch } = await makeDispatch(syscall, build); log.length = 0; // assume pre-build vatstore operations are correct const rootA = 'o+0'; @@ -96,7 +96,7 @@ test('liveslots pipelines to syscall.send', async t => { }, }); } - const dispatch = await makeDispatch(syscall, build); + const { dispatch } = await makeDispatch(syscall, build); log.length = 0; // assume pre-build vatstore operations are correct const rootA = 'o+0'; const x = 'o-5'; @@ -151,7 +151,7 @@ test('liveslots pipeline/non-pipeline calls', async t => { }, }); } - const dispatch = await makeDispatch(syscall, build); + const { dispatch } = await makeDispatch(syscall, build); log.length = 0; // assume pre-build vatstore operations are correct const rootA = 'o+0'; @@ -226,7 +226,7 @@ async function doOutboundPromise(t, mode) { }, }); } - const dispatch = await makeDispatch(syscall, build); + const { dispatch } = await makeDispatch(syscall, build); log.length = 0; // assume pre-build vatstore operations are correct const rootA = 'o+0'; @@ -329,7 +329,7 @@ test('liveslots retains pending exported promise', async t => { return root; } - const dispatch = await makeDispatch(syscall, build); + const { dispatch } = await makeDispatch(syscall, build); log.length = 0; // assume pre-build vatstore operations are correct const rootA = 'o+0'; const resultP = 'p-1'; @@ -373,7 +373,7 @@ async function doResultPromise(t, mode) { }, }); } - const dispatch = await makeDispatch(syscall, build); + const { dispatch } = await makeDispatch(syscall, build); log.length = 0; // assume pre-build vatstore operations are correct const rootA = 'o+0'; @@ -479,7 +479,7 @@ test('liveslots vs symbols', async t => { }, }); } - const dispatch = await makeDispatch(syscall, build); + const { dispatch } = await makeDispatch(syscall, build); log.length = 0; // assume pre-build vatstore operations are correct const rootA = 'o+0'; const target = 'o-1'; @@ -539,7 +539,7 @@ test('remote function call', async t => { }, }); } - const dispatch = await makeDispatch(syscall, build); + const { dispatch } = await makeDispatch(syscall, build); log.length = 0; // assume pre-build vatstore operations are correct const rootA = 'o+0'; @@ -676,15 +676,13 @@ test('capdata size limit on syscalls', async t => { }, }); } - const returnTestHooks = []; - const dispatch = await makeDispatch( + const { dispatch, testHooks } = await makeDispatch( syscall, build, 'vatA', {}, - returnTestHooks, ); - const { setSyscallCapdataLimits } = returnTestHooks[0]; + const { setSyscallCapdataLimits } = testHooks; setSyscallCapdataLimits(130, 1); log.length = 0; // assume pre-build vatstore operations are correct @@ -931,7 +929,7 @@ test('disable disavow', async t => { }, }); } - const dispatch = await makeDispatch(syscall, build, 'vatA', {}); + const { dispatch } = await makeDispatch(syscall, build, 'vatA', {}); log.length = 0; // assume pre-build vatstore operations are correct const rootA = 'o+0'; @@ -988,7 +986,7 @@ test('disavow', async t => { }); return root; } - const dispatch = await makeDispatch(syscall, build, 'vatA', { + const { dispatch } = await makeDispatch(syscall, build, 'vatA', { enableDisavow: true, }); log.length = 0; // assume pre-build vatstore operations are correct @@ -1041,7 +1039,7 @@ test('liveslots retains device nodes', async t => { return root; } - const dispatch = await makeDispatch(syscall, build); + const { dispatch } = await makeDispatch(syscall, build); const rootA = 'o+0'; const device = 'd-1'; await dispatch(makeMessage(rootA, 'first', [kslot(device)])); @@ -1069,7 +1067,7 @@ test('GC syscall.dropImports', async t => { }); return root; } - const dispatch = await makeDispatch(syscall, build, 'vatA', { + const { dispatch } = await makeDispatch(syscall, build, 'vatA', { enableDisavow: true, }); log.length = 0; // assume pre-build vatstore operations are correct @@ -1106,7 +1104,7 @@ test('GC syscall.dropImports', async t => { t.deepEqual(l3, { type: 'vatstoreGetNextKey', priorKey: 'vom.ir.o-1|', - result: 'vom.rc.o+6/1', + result: 'vom.rc.o+d6/1', }); // since nothing else is holding onto it, the vat should emit a dropImports @@ -1139,7 +1137,7 @@ test('GC dispatch.retireImports', async t => { }); return root; } - const dispatch = await makeDispatch(syscall, build, 'vatA', { + const { dispatch } = await makeDispatch(syscall, build, 'vatA', { enableDisavow: true, }); log.length = 0; // assume pre-build vatstore operations are correct @@ -1171,7 +1169,7 @@ test('GC dispatch.retireExports', async t => { }); return root; } - const dispatch = await makeDispatch(syscall, build, 'vatA', { + const { dispatch } = await makeDispatch(syscall, build, 'vatA', { enableDisavow: true, }); log.length = 0; // assume pre-build vatstore operations are correct @@ -1469,7 +1467,7 @@ test('GC dispatch.dropExports', async t => { }); return root; } - const dispatch = await makeDispatch(syscall, build, 'vatA', { + const { dispatch } = await makeDispatch(syscall, build, 'vatA', { enableDisavow: true, }); log.length = 0; // assume pre-build vatstore operations are correct @@ -1538,7 +1536,7 @@ test('GC dispatch.retireExports inhibits syscall.retireExports', async t => { }); return root; } - const dispatch = await makeDispatch(syscall, build, 'vatA', { + const { dispatch } = await makeDispatch(syscall, build, 'vatA', { enableDisavow: true, }); log.length = 0; // assume pre-build vatstore operations are correct @@ -1614,7 +1612,7 @@ test('simple promise resolution', async t => { }); return root; } - const dispatch = await makeDispatch(syscall, build, 'vatA', { + const { dispatch } = await makeDispatch(syscall, build, 'vatA', { enableDisavow: true, }); log.length = 0; // assume pre-build vatstore operations are correct @@ -1658,7 +1656,7 @@ test('promise cycle', async t => { }); return root; } - const dispatch = await makeDispatch(syscall, build, 'vatA', { + const { dispatch } = await makeDispatch(syscall, build, 'vatA', { enableDisavow: true, }); log.length = 0; // assume pre-build vatstore operations are correct @@ -1721,7 +1719,7 @@ test('unserializable promise resolution', async t => { }); return root; } - const dispatch = await makeDispatch(syscall, build, 'vatA', { + const { dispatch } = await makeDispatch(syscall, build, 'vatA', { enableDisavow: true, }); log.length = 0; // assume pre-build vatstore operations are correct @@ -1781,7 +1779,7 @@ test('unserializable promise rejection', async t => { }); return root; } - const dispatch = await makeDispatch(syscall, build, 'vatA', { + const { dispatch } = await makeDispatch(syscall, build, 'vatA', { enableDisavow: true, }); log.length = 0; // assume pre-build vatstore operations are correct @@ -1892,7 +1890,7 @@ test('result promise in args', async t => { }, }); } - const dispatch = await makeDispatch(syscall, build); + const { dispatch } = await makeDispatch(syscall, build); log.length = 0; // ignore pre-build vatstore operations const rootA = 'o+0'; const target = 'o-1'; diff --git a/packages/swingset-liveslots/test/test-vpid-liveslots.js b/packages/swingset-liveslots/test/test-vpid-liveslots.js index e0f2c384ff3d..3f28d355a507 100644 --- a/packages/swingset-liveslots/test/test-vpid-liveslots.js +++ b/packages/swingset-liveslots/test/test-vpid-liveslots.js @@ -170,7 +170,7 @@ async function doVatResolveCase1(t, mode) { }, }); } - const dispatch = await makeDispatch(syscall, build); + const { dispatch } = await makeDispatch(syscall, build); log.length = 0; // assume pre-build vatstore operations are correct const rootA = 'o+0'; @@ -322,7 +322,7 @@ async function doVatResolveCase23(t, which, mode, stalls) { }, }); } - const dispatch = await makeDispatch(syscall, build); + const { dispatch } = await makeDispatch(syscall, build); log.length = 0; // assume pre-build vatstore operations are correct const rootA = 'o+0'; @@ -572,7 +572,7 @@ async function doVatResolveCase4(t, mode) { four() {}, }); } - const dispatch = await makeDispatch(syscall, build); + const { dispatch } = await makeDispatch(syscall, build); log.length = 0; // assume pre-build vatstore operations are correct const rootA = 'o+0'; @@ -708,7 +708,7 @@ async function doVatResolveCase7(t, mode) { }, }); } - const dispatch = await makeDispatch(syscall, build); + const { dispatch } = await makeDispatch(syscall, build); log.length = 0; // assume pre-build vatstore operations are correct let nextVPID = 5; @@ -866,8 +866,8 @@ test('inter-vat circular promise references', async t => { }, }); } - const dispatchA = await makeDispatch(syscall, build, 'vatA'); - // const dispatchB = await makeDispatch(syscall, build, 'vatB'); + const { dispatch: dispatchA } = await makeDispatch(syscall, build, 'vatA'); + // const { dispatch: dispatchB } = await makeDispatch(syscall, build, 'vatB'); log.length = 0; // assume pre-build vatstore operations are correct const rootA = 'o+0'; diff --git a/packages/swingset-liveslots/test/virtual-objects/test-cease-recognition.js b/packages/swingset-liveslots/test/virtual-objects/test-cease-recognition.js index 2462385679e2..9f956b6f5a44 100644 --- a/packages/swingset-liveslots/test/virtual-objects/test-cease-recognition.js +++ b/packages/swingset-liveslots/test/virtual-objects/test-cease-recognition.js @@ -49,10 +49,10 @@ test('only enumerate virtual objects', async t => { // checked, not the baseref vrm.registerKind('5'); vrm.rememberFacetNames('5', ['facet0', 'facet1']); - vrm.ceaseRecognition('o+5/2'); - weakKeyCheck(t, log, 'o+5/2:0'); - weakKeyCheck(t, log, 'o+5/2:1'); - // skips 'vom.ir.o+5/2|' + vrm.ceaseRecognition('o+v5/2'); + weakKeyCheck(t, log, 'o+v5/2:0'); + weakKeyCheck(t, log, 'o+v5/2:1'); + // skips 'vom.ir.o+v5/2|' t.is(log.length, 0); // retiring an import does the weak-key check, even though the ID diff --git a/packages/swingset-liveslots/test/virtual-objects/test-virtualObjectGC.js b/packages/swingset-liveslots/test/virtual-objects/test-virtualObjectGC.js index 60745b093be6..49435cdbd6a3 100644 --- a/packages/swingset-liveslots/test/virtual-objects/test-virtualObjectGC.js +++ b/packages/swingset-liveslots/test/virtual-objects/test-virtualObjectGC.js @@ -133,11 +133,11 @@ let aWeakMap; let aWeakSet; const unfacetedThingKindID = '10'; -const unfacetedThingBaseRef = `o+${unfacetedThingKindID}`; +const unfacetedThingBaseRef = `o+v${unfacetedThingKindID}`; const facetedThingKindID = '11'; -const facetedThingBaseRef = `o+${facetedThingKindID}`; +const facetedThingBaseRef = `o+v${facetedThingKindID}`; const markerKindID = '13'; -const markerBaseRef = `o+${markerKindID}`; +const markerBaseRef = `o+v${markerKindID}`; function thingVref(isf, instance) { return `${isf ? facetedThingBaseRef : unfacetedThingBaseRef}/${instance}`; @@ -976,7 +976,7 @@ async function voRefcountManagementTest1(t, isf) { await dispatchMessageSuccessfully('prepareStore3'); // create three VOs (tag "holder") which hold our vref in their vdata - const holderVrefs = [2,3,4].map(instanceID => `o+${holderKindID}/${instanceID}`); + const holderVrefs = [2,3,4].map(instanceID => `o+v${holderKindID}/${instanceID}`); const holderdata = JSON.stringify({ held: kser(thing) }); // state of holders for (const holderVref of holderVrefs) { t.is(fakestore.get(`vom.${holderVref}`), holderdata); @@ -1017,7 +1017,7 @@ async function voRefcountManagementTest2(t, isf) { await dispatchMessageSuccessfully('prepareStore3'); // create three VOs (tag "holder") which hold our vref in their vdata - const holderVrefs = [2,3,4].map(instanceID => `o+${holderKindID}/${instanceID}`); + const holderVrefs = [2,3,4].map(instanceID => `o+v${holderKindID}/${instanceID}`); const vdata = JSON.stringify({ held: kser(thing) }); // state of holders for (const holderVref of holderVrefs) { t.is(fakestore.get(`vom.${holderVref}`), vdata); @@ -1058,7 +1058,7 @@ async function voRefcountManagementTest3(t, isf) { // make a linked list with virtual "holder" objects await dispatchMessageSuccessfully('prepareStoreLinked'); - const holderVrefs = [2,3,4].map(instanceID => `o+${holderKindID}/${instanceID}`); + const holderVrefs = [2,3,4].map(instanceID => `o+v${holderKindID}/${instanceID}`); t.is(fakestore.get(`vom.rc.${baseRef}`), '1'); // target held by holder[0] t.is(fakestore.get(`vom.rc.${holderVrefs[0]}`), '1'); // held by holder[1] @@ -1109,7 +1109,7 @@ test.serial('presence refcount management 1', async t => { // create three VOs (tag "holder") which hold our vref in their vdata await dispatchMessageSuccessfully('prepareStore3'); - const holderVrefs = [2,3,4].map(instanceID => `o+${holderKindID}/${instanceID}`); + const holderVrefs = [2,3,4].map(instanceID => `o+v${holderKindID}/${instanceID}`); const holderdata = JSON.stringify({ held: kser(presence) }); // state of holders for (const holderVref of holderVrefs) { t.is(fakestore.get(`vom.${holderVref}`), holderdata); @@ -1147,7 +1147,7 @@ test.serial('presence refcount management 2', async t => { await dispatchMessageSuccessfully('prepareStore3'); - const holderVrefs = [2,3,4].map(instanceID => `o+${holderKindID}/${instanceID}`); + const holderVrefs = [2,3,4].map(instanceID => `o+v${holderKindID}/${instanceID}`); const holderdata = JSON.stringify({ held: kser(presence) }); // state of holders for (const holderVref of holderVrefs) { t.is(fakestore.get(`vom.${holderVref}`), holderdata); @@ -1195,7 +1195,7 @@ test.serial('remotable refcount management 1', async t => { t.is(fakestore.get(`vom.rc.${vref}`), undefined); // however all three holders should have the vref in their vdata - const holderVrefs = [2,3,4].map(instanceID => `o+${holderKindID}/${instanceID}`); + const holderVrefs = [2,3,4].map(instanceID => `o+v${holderKindID}/${instanceID}`); const holderdata = JSON.stringify({ held: kser(remotable) }); // state of holders for (const holderVref of holderVrefs) { t.is(fakestore.get(`vom.${holderVref}`), holderdata); @@ -1223,7 +1223,7 @@ test.serial('remotable refcount management 2', async t => { await dispatchMessageSuccessfully('prepareStore3'); await dispatchMessageSuccessfully('finishDropHolders'); // all three holders are gone - const holderVrefs = [2,3,4].map(instanceID => `o+${holderKindID}/${instanceID}`); + const holderVrefs = [2,3,4].map(instanceID => `o+v${holderKindID}/${instanceID}`); for (const holderVref of holderVrefs) { t.is(fakestore.get(`vom.${holderVref}`), undefined); } @@ -1325,7 +1325,7 @@ test.serial('VO holding non-VO', async t => { const holderKindID = JSON.parse(fakestore.get(`idCounters`)).exportID - 2; t.is(JSON.parse(fakestore.get(`vom.vkind.${holderKindID}`)).tag, 'holder'); // holder is first instance created of that kind - const holderVref = `o+${holderKindID}/1`; + const holderVref = `o+v${holderKindID}/1`; // Lerv -> LERv Export non-VO const value = await dispatchMessageSuccessfully('exportHeld'); diff --git a/packages/swingset-liveslots/test/virtual-objects/test-virtualObjectManager.js b/packages/swingset-liveslots/test/virtual-objects/test-virtualObjectManager.js index 36f9a7a0112c..3c6fc42cc255 100644 --- a/packages/swingset-liveslots/test/virtual-objects/test-virtualObjectManager.js +++ b/packages/swingset-liveslots/test/virtual-objects/test-virtualObjectManager.js @@ -112,7 +112,7 @@ test('multifaceted virtual objects', t => { }, }, ); - const kid = 'o+2'; + const kid = 'o+v2'; const { incr, decr } = makeMultiThing('foo'); t.is(incr.getName(), 'foo'); t.is(incr.getCount(), 0); @@ -166,9 +166,9 @@ test('virtual object operations', t => { const { defineKind, flushCache, dumpStore } = makeFakeVirtualObjectManager({ cacheSize: 3, log }); const makeThing = defineKind('thing', initThing, thingBehavior); - const tid = 'o+2'; + const tid = 'o+v2'; const makeZot = defineKind('zot', initZot, zotBehavior); - const zid = 'o+3'; + const zid = 'o+v3'; // phase 0: start t.deepEqual(dumpStore(), [ @@ -418,7 +418,7 @@ test('symbol named methods', t => { }; const makeThing = defineKind('symthing', initThing, symThingBehavior); - const tid = 'o+2'; + const tid = 'o+v2'; // phase 0: start t.deepEqual(dumpStore(), [ @@ -547,7 +547,7 @@ test('durable kind IDs can be reanimated', t => { 'set vom.dkind.10 {"kindID":"10","tag":"testkind","nextInstanceID":1}', ); t.deepEqual(log, []); - const khid = `o+1/10`; + const khid = `o+d1/10`; const kind = kslot(khid, 'kind'); // Store it in the store without having used it @@ -592,7 +592,7 @@ test('durable kind IDs can be reanimated', t => { log.shift(), 'set vom.dkind.10 {"kindID":"10","tag":"testkind","nextInstanceID":2,"unfaceted":true}', ); - t.is(log.shift(), `set vom.o+10/1 ${thingVal(0, 'laterThing', 0)}`); + t.is(log.shift(), `set vom.o+d10/1 ${thingVal(0, 'laterThing', 0)}`); t.deepEqual(log, []); }); @@ -604,7 +604,7 @@ test('virtual object gc', t => { const { deleteEntry, dumpStore } = fakeStuff; const makeThing = defineKind('thing', initThing, thingBehavior); - const tbase = 'o+10'; + const tbase = 'o+v10'; const makeRef = defineKind('ref', value => ({ value }), { setVal: ({ state }, value) => { state.value = value; diff --git a/packages/swingset-liveslots/test/virtual-objects/test-weakcollections-vref-handling.js b/packages/swingset-liveslots/test/virtual-objects/test-weakcollections-vref-handling.js index 589054959cf7..fae18509b663 100644 --- a/packages/swingset-liveslots/test/virtual-objects/test-weakcollections-vref-handling.js +++ b/packages/swingset-liveslots/test/virtual-objects/test-weakcollections-vref-handling.js @@ -45,7 +45,7 @@ test('weakMap vref handling', async t => { checkMap('o-1', 'imported presence', true); checkMap('o+2', 'exported remotable', false); - checkMap('o+3/4', 'exported virtual object', true); + checkMap('o+v3/4', 'exported virtual object', true); checkMap('p-5', 'imported promise', false); checkMap('p+6', 'exported promise', false); checkMap('d-7', 'imported device', false); @@ -71,7 +71,7 @@ test('weakMap vref handling', async t => { checkSet('o-8', true); checkSet('o+9', false); - checkSet('o+10/11', true); + checkSet('o+v10/11', true); checkSet('p-12', false); checkSet('p+13', false); checkSet('d-14', false); diff --git a/packages/swingset-liveslots/tools/fakeVirtualSupport.js b/packages/swingset-liveslots/tools/fakeVirtualSupport.js index e9a551ebaadf..b8639f998d16 100644 --- a/packages/swingset-liveslots/tools/fakeVirtualSupport.js +++ b/packages/swingset-liveslots/tools/fakeVirtualSupport.js @@ -181,18 +181,18 @@ export function makeFakeLiveSlotsStuff(options = {}) { } function convertSlotToVal(slot) { - const { type, virtual, facet, baseRef } = parseVatSlot(slot); + const { type, virtual, durable, facet, baseRef } = parseVatSlot(slot); assert.equal(type, 'object'); let val = getValForSlot(baseRef); if (val) { - if (virtual) { + if (virtual || durable) { if (facet !== undefined) { return val[facet]; } } return val; } - if (virtual) { + if (virtual || durable) { if (vrm) { val = vrm.reanimate(slot); if (facet !== undefined) {