-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- set off governance for upgrade - cite other relevant docs publicFacet was a poor example of baggage key
- Loading branch information
Showing
1 changed file
with
186 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
::: |