Skip to content

Commit

Permalink
enforce that buildRootObject() returns an ephemeral, not durable
Browse files Browse the repository at this point in the history
We assign the special "o+0" vref to the root object. Newly created
virtual/durable objects are assigned a vref like "o+d13/5", which
incorporates the KindID and the instance sequence number.

If buildRootObject() were to create a virtual/durable object and
return it as the root, we would now have one object with two different
identities, violating the main slotToVal/valToSlot invariant. Under
the new/upcoming VOM implementation, the initial Representative will
have a context and a state that was created and cached under the
durable vref, but by the time a method is called, valToSlot knows it
as "o+0", so the vatstoreGet state lookup fails (as of course there is
no state stored under "o+0").

(Under the old/current VOM, this sometimes worked by accident, but we
believe there were cases where it would have failed)

This commit inserts a liveslots check to ensure that the root object
does not already have a vref. The documentation is updated to remind
userspace vat authors of the constraint: `buildRootObject` must return
an ephemeral, as created with `Far` or `makeExo`.

It also changes vattp to use an ephemeral root. Fortunately, this was
the only case we could find of an existing vat using a durable root.

closes #7240
  • Loading branch information
warner committed Mar 25, 2023
1 parent 324eee0 commit 03c779e
Show file tree
Hide file tree
Showing 5 changed files with 21 additions and 14 deletions.
8 changes: 6 additions & 2 deletions packages/SwingSet/docs/static-vats.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ Static vats are defined by a JS module file which exports a function named `buil

The `buildRootObject` function will be called with one object, named `vatPowers`. The contents of `vatPowers` are subject to change, but in general it provides pure functions which are inconvenient to access as imports, and vat-specific authorities that are not easy to express through syscalls. See below for the current property list.

`buildRootObject` is expected to return a hardened object with callable methods and no data properties (note that `harden` is available as a global). For example:
`buildRootObject` is expected to return a hardened "Remotable" object with callable methods and no data properties. The best way to do this is with the `Far` function:

```js
import { Far } from '@endo/far';

export function buildRootObject(vatPowers) {
let counter = 0;
return harden({
return Far('root', {
increment() {
counter += 1;
},
Expand All @@ -30,6 +32,8 @@ export function buildRootObject(vatPowers) {
}
```

The root object *must* be an "ephemeral" object, i.e. created with `Far`. It cannot be a virtual or durable object (created with a maker returned by `defineKind` or `defineDurableKind`, or the vat-data convenience wrappers like `prepareSingleton`). This ensures that the root object's identity is stable across upgrade.

Each vat has a name. A *Presence* for this vat's root object will be made available to the bootstrap function, in its `vats` argument. For example, if this vat is named `counter`, then the bootstrap function could do:

```js
Expand Down
14 changes: 6 additions & 8 deletions packages/SwingSet/docs/vat-upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Vat upgrade is triggered by an `upgrade()` message to the vat's "adminNode" cont
business
* messages from other vats start to arrive at the v2 code

The first time the v2 code is invoked is called the "upgrade phase", and represents a limited window of time (a single crank) during which v2 must perform a number of tasks. The v2 code can use Promises to defer work into the (very) near future, however all this work must be complete by the time the promise queue is drained.
The first time the v2 code is invoked is called the "upgrade phase", and represents a limited window of time (a single crank) during which v2 must perform a number of tasks. The v2 code can use Promises to defer work into the (very) near future, however all this work must be complete by the time the promise queue is drained. This means `buildRootObject` may not `await` messages sent off-vat, because their responses cannot return before the initial `startVat` delivery is complete.

If a large number of data records need updating, the v2 code can (and should) use lazy/on-demand data migration, to avoid doing too much work in a single crank. However, the new vat only has a single upgrade-phase crank to prepare for incoming messages. So any lazy migration must be prepared to handle arbitrary messages despite the migration not being complete.

Expand Down Expand Up @@ -63,6 +63,8 @@ During the upgrade phase, v2 code is obligated to re-define all durable Kinds cr

As a special case, the root object returned from v2's `buildRootObject()` is automatically associated with exportID `o+0` (see [How Liveslots Uses the Vatstore](../../swingset-liveslots/src/vatstore-usage.md#counters)) and is therefore also obligated to support the same methods as its predecessor. This means that the root object is effectively always durable, and should not be explicitly persisted.

To be precise, the root object *must* be an "ephemeral" object, i.e. created with `Far` or `makeExo()`. It cannot be a virtual or durable object (created with a maker returned by `defineKind` or `defineDurableKind`, or the vat-data convenience wrappers like `prepareExo` or `prepareSingleton`). This ensures that the root object's identity is stable across upgrades.

### Zone API

The [zone API](https://github.com/Agoric/agoric-sdk/tree/master/packages/zone#readme) provides a unified model for creating the objects mentioned above, regardless of their backing storage:
Expand Down Expand Up @@ -196,7 +198,7 @@ An important property of `options` is `vatParameters`. This value is passed to t

The v2 code wakes up inside the upgrade phase when `buildRootObject(vatPowers, vatParameters, baggage)` is called, where `vatParameters` will come from the call to `upgrade`. This `buildRootObject()` is expected to return an object, or a Promise that resolves to an object, and that object will assume the identity of the root object.

Before completion of `buildRootObject()` is indicated by either returning a non-promise or by fulfilling a returned promise, the v2 code is obligated to redefine every Kind that was created by the v1 code. If any durable Kinds are defined incompletely or left undefined by the time of that indication, the upgrade fails and the vat is rolled back to v1.
Before completion of `buildRootObject()` is indicated (either by returning a non-promise or by fulfilling a returned promise), the v2 code is obligated to redefine every Kind that was created by the v1 code. If any durable Kinds are defined incompletely or left undefined by the time of that indication, the upgrade fails and the vat is rolled back to v1.

```js
import { M } from '@agoric/store';
Expand All @@ -206,10 +208,6 @@ const FooI = M.interface('foo', fooMethodGuards);
const makeFoo = prepareExoClass(someDurableMap, 'foo', fooI, initFoo, fooMethods);
```

It also needs to reattach every singleton `Far()` object exported by the v1 code.

When `buildRootObject()` finishes and the upgrade phase completes successfully, the kernel will reject all Promises that v1 had exported (specifically all promises for which v1 was the "decider"). It will terminate any non-durable exports made by v1, and external vats which imported those objects will find themselves holding a broken reference (i.e. every message sent to it will be rejected with an Error, just as if they were exported by a vat which was then terminated).

TBD: we might terminate any Durable exported objects which v2 does not reattach, or we might treat that as an error.
When `buildRootObject()` finishes and the upgrade phase completes successfully, the kernel will reject all Promises that v1 had exported (specifically all promises for which v1 was the "decider"). It will abandon any non-durable exports made by v1, and external vats which imported those objects will find themselves holding a broken reference (i.e. every message sent to it will be rejected with an Error, just as if they were exported by a vat which was then terminated).

(TODO) If the v2 code experiences an error during the upgrade phase, the entire upgrade is aborted and the v1 code is reinstated.
If the v2 code experiences an error during the upgrade phase, the entire upgrade is aborted and the v1 code is reinstated. The caller of `E(adminNode).upgrade()` will observe their result promise get rejected.
7 changes: 3 additions & 4 deletions packages/SwingSet/src/vats/vattp/vat-vattp.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import {
provideDurableMapStore,
provideDurableSetStore,
provideKindHandle,
prepareSingleton,
} from '@agoric/vat-data';
import { E } from '@endo/eventual-send';
import { Far } from '@endo/marshal';

// See ../../docs/delivery.md for a description of the architecture of the
// comms system.
Expand All @@ -28,7 +28,6 @@ export function buildRootObject(vatPowers, _vatParams, baggage) {
const { D } = vatPowers;

// Define all durable baggage keys and kind handles.
const serviceSingletonBaggageKey = 'vat-tp handler';
const mailboxDeviceBaggageKey = 'mailboxDevice';
const mailboxHandle = provideKindHandle(baggage, 'mailboxHandle');
const mailboxMapBaggageKey = 'mailboxes';
Expand Down Expand Up @@ -285,8 +284,8 @@ export function buildRootObject(vatPowers, _vatParams, baggage) {
},
};

// Expose a durable service singleton.
return prepareSingleton(baggage, serviceSingletonBaggageKey, {
// Expose the service
return Far('vat-tp handler', {
...serviceMailboxFunctions,
...serviceNetworkFunctions,
});
Expand Down
2 changes: 2 additions & 0 deletions packages/swingset-liveslots/docs/liveslots.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ See `static-vats.md`, `dynamic-vats.md`, and `vat-environment.md` in this direct

This function returns the "root object". A remote reference to it will be made available to the bootstrap vat, which can use it to trigger whatever initialization needs to happen.

The root object *must* be an "ephemeral" object, i.e. created with `Far`. It cannot be a virtual or durable object (created with a maker returned by `defineKind` or `defineDurableKind`, or the vat-data convenience wrappers like `prepareSingleton`). This ensures that the root object's identity is stable across upgrade.

## Returning New Objects

```js
Expand Down
4 changes: 4 additions & 0 deletions packages/swingset-liveslots/src/liveslots.js
Original file line number Diff line number Diff line change
Expand Up @@ -1437,6 +1437,10 @@ function build(
);
getInterfaceOf(rootObject) !== undefined ||
Fail`buildRootObject() for vat ${forVatID} returned ${rootObject} with no interface`;
if (valToSlot.has(rootObject)) {
Fail`buildRootObject() must return ephemeral, not virtual/durable object`;
}

// Need to load watched promises *after* buildRootObject() so that handler kindIDs
// have a chance to be reassociated with their handlers.
watchedPromiseManager.loadWatchedPromiseTable(unmeteredRevivePromise);
Expand Down

0 comments on commit 03c779e

Please sign in to comment.