From cbd9f3b82a4a1a74508a56fb346ee4197ec33d82 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 6 Feb 2024 18:46:38 -0600 Subject: [PATCH] docs: draft of durability - set off governance for upgrade - cite other relevant docs publicFacet was a poor example of baggage key --- main/guides/zoe/contract-upgrade.md | 186 ++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 main/guides/zoe/contract-upgrade.md diff --git a/main/guides/zoe/contract-upgrade.md b/main/guides/zoe/contract-upgrade.md new file mode 100644 index 0000000000..614903eea1 --- /dev/null +++ b/main/guides/zoe/contract-upgrade.md @@ -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. +_See [@endo/patterns](https://endojs.github.io/endo/modules/_endo_patterns.html) for more on interface guards._ + +```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) + +:::