Skip to content

Commit

Permalink
fix: add 'v'/'d' virtual/durable annotations to vrefs
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
warner and mhofman committed Feb 7, 2023
1 parent 7cd4bcf commit c4eeff3
Show file tree
Hide file tree
Showing 26 changed files with 428 additions and 174 deletions.
66 changes: 66 additions & 0 deletions packages/SwingSet/docs/c-lists.md
Original file line number Diff line number Diff line change
@@ -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.
32 changes: 24 additions & 8 deletions packages/SwingSet/src/lib/parseVatSlots.js
Original file line number Diff line number Diff line change
Expand Up @@ -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:
*
Expand All @@ -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.
*
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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';
Expand All @@ -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 {
Expand All @@ -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 };
}

/**
Expand Down
28 changes: 14 additions & 14 deletions packages/SwingSet/test/virtualObjects/test-representatives.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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`]:
Expand All @@ -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',
});
});

Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions packages/swingset-liveslots/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
10 changes: 7 additions & 3 deletions packages/swingset-liveslots/src/collectionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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];
Expand Down Expand Up @@ -782,6 +782,7 @@ export function makeCollectionManager(
const [vobjID, collection] = makeCollection(
label,
kindName,
durable,
keyShape,
valueShape,
);
Expand Down Expand Up @@ -828,6 +829,7 @@ export function makeCollectionManager(
const [vobjID, collection] = makeCollection(
label,
kindName,
durable,
keyShape,
valueShape,
);
Expand Down Expand Up @@ -855,6 +857,7 @@ export function makeCollectionManager(
const [vobjID, collection] = makeCollection(
label,
kindName,
durable,
keyShape,
valueShape,
);
Expand Down Expand Up @@ -884,6 +887,7 @@ export function makeCollectionManager(
const [vobjID, collection] = makeCollection(
label,
kindName,
durable,
keyShape,
valueShape,
);
Expand Down
24 changes: 13 additions & 11 deletions packages/swingset-liveslots/src/liveslots.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -752,19 +753,19 @@ 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];
}
}
return val;
}
let result;
if (virtual) {
if (virtual || durable) {
assert.equal(type, 'object');
val = vrm.reanimate(baseRef);
if (facet !== undefined) {
Expand Down Expand Up @@ -1237,20 +1238,20 @@ 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');
}
}
}

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
Expand Down Expand Up @@ -1338,6 +1339,7 @@ function build(
...vrm.testHooks,
...collectionManager.testHooks,
setSyscallCapdataLimits,
vatGlobals,
});

function setVatOption(option, value) {
Expand Down
Loading

0 comments on commit c4eeff3

Please sign in to comment.