Skip to content

Commit

Permalink
docs: draft of durability
Browse files Browse the repository at this point in the history
 - set off governance for upgrade
 - cite other relevant docs

publicFacet was a poor example of baggage key
  • Loading branch information
dckc committed Feb 9, 2024
1 parent 1acd85d commit cbd9f3b
Showing 1 changed file with 186 additions and 0 deletions.
186 changes: 186 additions & 0 deletions main/guides/zoe/contract-upgrade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Contract Upgrade

The result of starting a contract includes the right to upgrade the contract. A call to [E(zoe).install(...)](/reference/zoe-api/zoe.md#e-zoe-startinstance-installation-issuerkeywordrecord-terms-privateargs) returns a record of several objects that represent different levels of access.
The `publicFacet` and `creatorFacet` are defined by the contract.
The `adminFacet` is defined by Zoe and includes methods to upgrade the contract.

::: tip Upgrade Governance

Governance of the right to upgrade is a complex topic that we cover only briefly here.

- When [BLD staker governance](https://community.agoric.com/t/about-the-governance-category/15) makes a decision to start a contract using [swingset.CoreEval](../coreeval/),
to date, the `adminFacet` is stored in the bootstrap vat, allowing
the BLD stakers to upgrade such a contract in a later `swingset.CoreEval`.
- The `adminFacet` reference can be discarded, so that noone can upgrade
the contract from within the JavaScript VM. (BLD staker gonvernace
could, in theory, change the VM itself.)
- The `adminFacet` can be managed using the [@agoric/governance](https://github.com/Agoric/agoric-sdk/tree/master/packages/governance#readme) framework; for example, using the `committee.js` contract.

:::

Upgrading a contract instance means re-starting the contract using a different [code bundle](./#bundling-a-contract). Suppose we start a contract as usual, using
the bundle ID of a bundle we already sent to the chain:

```js
const bundleID = 'b1-1234abcd...';
const installation = await E(zoe).installBundleID(bundleID);
const { instance, ... facets } = await E(zoe).startInstance(installation, ...);

// ... use facets.publicFacet, instance etc. as usual
```

If we have the `adminFacet` and the bundle ID of a new version,
we can use the `upgradeContract` method to upgrade the contract instance:

```js
const v2BundleId = 'b1-feed1234...`; // hash of bundle with new feature
const { incarnationNumber } = await E(facets.adminFacet).upgradeContract(v2BundleId);
```
The `incarnationNumber` is 1 after the 1st upgrade, 2 after the 2nd, and so on.
::: details re-using the same bundle
Note that a "null upgrade" that re-uses the original bundle is valid, and a legitimate approach to deleting accumulated heap state.
See also `E(adminFacet).restartContract()`.
:::
## Upgradable Contracts
There are a few requirements for the contract that differ from non-upgradable contracts:
1. [Upgradable Declaration](#upgradable-declaration)
2. [Durability](#durability)
3. [Kinds](#kinds)
4. [Crank](#crank)
### Upgradable Declaration
The new code bundle declares that it supports upgrade by exporting a `prepare` function in place of `start`.
<<< @/snippets/zoe/src/02b-state-durable.js#export-prepare
### Durability
The 3rd argument, `baggage`, of the `prepare` function is a `MapStore`
that provides a way to preserve state and behavior of objects
between incarnations in a way that preserves identity of objects
as seen from other vats:
```js
let rooms;
if (!baggage.has('rooms')) {
// initial incarnation: create the object
rooms = makeScalarBigMapStore('rooms', { durable: true });
baggage.init('rooms', rooms);
} else {
// subsequent incarnation: use the object from the initial incarnation
rooms = baggage.get('rooms');
}
```
The `provide` function supports a concise idiom for this find-or-create pattern:
```js
import { provide } from '@agoric/vat-data';
const rooms = provide(baggage, 'rooms', () =>
makeScalarBigMapStore('rooms', { durable: true }),
);
```
The `zone` API is a convenient way to manage durability. Its store methods integrate the `provide` pattern:
::: details import { makeDurableZone } ...
<<< @/snippets/zoe/src/02b-state-durable.js#import-zone
:::
<<< @/snippets/zoe/src/02b-state-durable.js#zone1
::: details What happens if we don't use baggage?

When the contract instance is restarted, it gets a fresh [heap](../js-programming/#vats-the-unit-of-synchrony), so [ordinary heap state](./contract-basics.html#state) does not survive upgrade. This implementation does not persist the effect of `E(publicFacet).set(2)` from the first incarnation:

<<< @/snippets/zoe/src/02-state.js#heap-state{2}

Also, it creates a fresh `publicFacet` object each time. So messages
from clients that try to use the `publicFacet` from the first incarnation
will fail to reach the `publicFacet` created in later incarnations.

<<< @/snippets/zoe/src/02-state.js#fresh-export{2}

:::

### Kinds

Use `zone.exoClass()` to define state and methods of kinds of durable objects such as `Room`:

::: details import { makeDurableZone } ...

<<< @/snippets/zoe/src/02b-state-durable.js#import-provide-zone{1}

:::

<<< @/snippets/zoe/src/02b-state-durable.js#exoclass

Defining `publicFacet` as a singleton `exo` allows clients to
continue to use it after an upgrade:

<<< @/snippets/zoe/src/02b-state-durable.js#exo

Now we have all the parts of an upgradable contract.

::: details full contract listing

<<< @/snippets/zoe/src/02b-state-durable.js#contract

:::

We can then upgrade it to have another method:

```js
const makeRoom = zone.exoClass('Room', RoomI, (id) => ({ id, value: 0 }), {
...
clear(delta) {
this.state.value = 0;
},
});
```

The interface guard also needs updating.
<small>_See [@endo/patterns](https://endojs.github.io/endo/modules/_endo_patterns.html) for more on interface guards._</small>

```js
const RoomI = M.interface('Room', {
...
clear: M.call().returns(),
});
```

::: tip Notes

- Once the state is defined by the `init` function (3rd arg), properties cannot be added or removed.
- Values of state properties must be serializable.
- Values of state properties are hardened on assignment.
- You can replace the value of a state property (e.g. `state.zot = [...state.zot, 'last']`), and you can update stores (`state.players.set(1, player1)`), but you cannot do things like `state.zot.push('last')` nor `state.jot = { x: 1 }; state.jot.x = 2`.
- The tag (1st arg) is used to form a key in `baggage`, so take care to avoid collisions. `zone.subZone()` may be used to partition namespaces.
- See also [defineExoClass](https://endojs.github.io/endo/functions/_endo_exo.defineExoClass.html) for further detail `zone.exoClass`.
- To define multiple objects that share state, use `zone.exoClassKit`.
- See also [defineExoClassKit](https://endojs.github.io/endo/functions/_endo_exo.defineExoClassKit.html)
- For an extended test / example, see [test-coveredCall-service-upgrade.js](https://github.com/Agoric/agoric-sdk/blob/master/packages/zoe/test/swingsetTests/upgradeCoveredCall/test-coveredCall-service-upgrade.js).
:::
## Crank
Define all exo classes/kits before any incoming method calls from other vats -- in the first "crank".
::: tip Note
- For more on crank constraints, see [Virtual and Durable Objects](https://github.com/Agoric/agoric-sdk/blob/master/packages/SwingSet/docs/virtual-objects.md#virtual-and-durable-objects) in [SwingSet docs](https://github.com/Agoric/agoric-sdk/tree/master/packages/SwingSet/docs)
:::

0 comments on commit cbd9f3b

Please sign in to comment.