-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Durable contracts #1255
Durable contracts #1255
Changes from all commits
159b0a4
f47fd29
5a72a8c
8368811
c80699b
0335f86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
# Durable Contract Details | ||
|
||
Zoe allows you to write smart contracts that manage interactions between cooperative, but possibly untrusting, | ||
parties. Some contracts perform a single simple task like trading one good for another, and then are | ||
done. Others stay around and manage assets and interactions over time. The first kind don't need persistence | ||
or the ability to upgrade, while the second kind need to make use of more sophisticated functionality. | ||
|
||
When a contract is intended to continue running and serving many customers over a long time, its objects and | ||
data need to be persistent, its owners or managers may need to be able to adjust parameters or perform other | ||
governance actions, and they may want it to be upgradeable so that the code can adapt over time. | ||
|
||
## Durable Objects | ||
|
||
The first step toward contracts that can be upgraded is storing all the data that a later incarnation will | ||
need. This means putting relevant state in [Baggage](/guides/zoe/contract-upgrade#baggage), and ensuring that | ||
reachable objects that will be accessible to clients have an identity that can be maintained as the behavior | ||
changes with contract upgrades. | ||
|
||
We use zone.exo(), zone.exoClass(), and zone.exoClassKit() to define durable objects. | ||
|
||
[Zone](/glossary/#zone) provides an interface for defining objects and classes that supports both ephemeral | ||
objects (allocated on the heap), and durable objects that can persist and that | ||
[SwingSet](/guides/platform/#swingset) will page in or out on demand. | ||
|
||
Our persistent objects are designed to encapsulate their state, and can present different facets to different | ||
clients to distinguish different aspects of authority. `zone.exoClass()` defines a kind with a single facet, | ||
while `zone.exoClassKit()` defines a kind with multiple facets. (Here are some relevant [naming | ||
conventions](/guides/ertp/#method-naming-structure).) | ||
|
||
``` | ||
zone.exoClassKit(tag, guard, init, methodKit, options) | ||
zone.exoClass(tag, guard, init, methods, options) | ||
zone.exo(tag, guard, methods, options) | ||
``` | ||
|
||
The next several sub-sections explain how the parameters to those functions are used. | ||
|
||
### Tag: naming kinds of objects | ||
|
||
The `tag` provides the identity of the kind of object, which is associated with the defined behavior. When a | ||
contract (or other vat) is restarted or upgraded, SwingSet requires that all kinds that were previously | ||
defined in a vat be defined again. With an upgrade, the behavior can be changed. We use the term "null | ||
upgrade" to refer to upgrades that don't change the behavior. | ||
|
||
### Init: specifying state | ||
|
||
The `init` parameter defines a function that is used to define the state associated with each instance of an | ||
object. Notice that `exo()` doesn't take `init` as a parameter; this limits the defined (singleton) object to | ||
referencing values defined in the enclosing scope. Classes can also refer to variables in their defining | ||
scope, but any values inherited from the scope will be accessible to all instances of that type. The `init` | ||
function's parameters become the parameters of the maker function returned by `exoClass()` and | ||
`exoClassKit()`. `init`'s return value is the state that will be preserved by SwingSet for each | ||
object. `exo()` doesn't have an init parameter, so it creates and returns the singleton immediately rather | ||
than returning a maker. | ||
|
||
The Exo objects (or just "Exos") defined by these functions are both persistent and virtualizable. SwingSet | ||
knows their complete state, so it can page them out when space is tight, and page them back in when they are | ||
referenced again. | ||
|
||
Fields of the object returned by the `init` function become fields of the persistent state. (_These cannot | ||
currently be changed on upgrade, though we're considering relaxing that restriction._) Within methods they can | ||
each be accessed as fields of `this.state`. Our convention is to extract the fields that will be used in a | ||
method on its first line, like `const { a, b } = this.state;` Once that has been done, those variable can be | ||
read or written as normal javascript variables, and the values will persist. (For more details, see [the note | ||
here](/guides/zoe/contract-upgrade.html#kinds)). | ||
|
||
### Methods: defining behavior | ||
|
||
The methods argument of `exoClass()` is a record of methods, written in [concise method | ||
syntax](https://github.com/Agoric/agoric-sdk/wiki/Arrow-Function-Style#far-classes-do-not-use-arrow-function-style). | ||
`exoClass()` defines a single facet. | ||
|
||
The methodKit parameter to `exoClassKit` is a record that can define multiple facets. The keys give the names | ||
of the facets, and each value defines the methods of the corresponding facet. All facets of each object share | ||
access to the entire state defined in `init`, but each facet is a separate capability. Within the methods, | ||
other facets can be reached by name within `this.facets`. The maker returned by `exoClassKit()` builds a new | ||
object each time it is called, and returns all the facets. The caller can decide which of the facets to hold | ||
or to pass to separate recipients. | ||
|
||
### Guards: defensive methods | ||
|
||
These objects and facets are designed to be durable and shareable across address space boundaries. Since code | ||
in different vats might not be mutually trusting, code needs to be defensive about parameters received from | ||
remote callers. Interface Guards let us express in code what parameters each method expects and can handle | ||
safely. If a caller provides a parameter that doesn't match the template specified in the guard, SwingSet | ||
returns an exception to the caller without notifying the receiver. If the return value doesn't match, the | ||
function returns an exception to the caller. | ||
|
||
`exoClass()` takes a guard for a single interface, defined by | ||
|
||
``` | ||
M.interface('name', { | ||
methodA: M.call(paramsAShape).returns(resultAShape), | ||
methodB: M.callWhen(M.await(paramsBShape)).returns(resultBShape), | ||
} | ||
``` | ||
|
||
`M.call()` verifies that all parameters match the guard before passing them through to the | ||
method. `M.callWhen(M.await(paramsBGuard))` awaits the resolution of the parameter, and then verifies that the | ||
result matches before invoking the method. When a guard is written this latter way, the method doesn't have to | ||
be `async`. In both cases, the method doesn't have to check for compliance with the guard. | ||
|
||
[Shapes can specify](https://endojs.github.io/endo/interfaces/_endo_patterns.PatternMatchers.html) simple | ||
types like `M.string()`, `M.remotable()`, and `M.number()`, as well as complex structures of records and | ||
arrays. The list of parameters can specify required and optional parameters, as well as allowing unspecified | ||
rest parameters. | ||
|
||
If you want to make use of the power of this type-checking within methods, you can call `mustMatch(specimen, | ||
pattern)` or `matches(specimen, pattern)` directly. The former throws if the pattern doesn't match, while the | ||
latter returns a boolean. | ||
|
||
### Options: finish and stateShape | ||
|
||
All the type definers can also take an [options | ||
argument](https://endojs.github.io/endo/types/_endo_exo.FarClassOptions.html) at the end, which is commonly | ||
used for a `finish()` function to complete initialization, or a stateShape, which can enforce invariants on | ||
the state values. | ||
|
||
`finish()`, if provided, is called after instances have been created but before they are returned to the | ||
caller. They can be used to send an initial state update, or to complete initialization which couldn't be done | ||
in `init`. `finish` has access to state and facets if needed. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,12 @@ | ||
# Contract Upgrade | ||
|
||
The result of starting a contract includes the right to upgrade the contract instance. A call to [E(zoe).startInstance(...)](/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. | ||
The return value when starting a contract includes a capability to upgrade the contract instance. A call to | ||
[E(zoe).startInstance(...)](/reference/zoe-api/zoe.md#e-zoe-startinstance-installation-issuerkeywordrecord-terms-privateargs) | ||
returns a [kit](/guides/ertp/#method-naming-structure) of [facets](/glossary/#facet); that is a | ||
record of several objects that represent different ways to access the contract instance. The | ||
`publicFacet` and `creatorFacet` are defined by the contract. The | ||
[`adminFacet`](/reference/zoe-api/zoe.html#adminFacet) is defined by Zoe and includes methods to | ||
upgrade the contract. | ||
|
||
::: tip Upgrade Governance | ||
|
||
|
@@ -60,16 +64,26 @@ There are a few requirements for the contract that differ from non-upgradable co | |
|
||
### Upgradable Declaration | ||
|
||
The new code bundle declares that it supports upgrade by exporting a `prepare` function in place of `start`. | ||
The new code bundle declares that it supports upgrade by including a `meta` record in addition to | ||
`start`. (_We used to indicate upgradability by using `prepare` instead of `start`, but that | ||
approach is deprecated._) | ||
|
||
<<< @/../snippets/zoe/src/02b-state-durable.js#export-prepare | ||
`meta` is a record with any or all of `upgradability`, `customTermsShape`, and `privateArgsShape` | ||
defined. The latter two are optional | ||
[Patterns](https://endojs.github.io/endo/modules/_endo_patterns.html) restricting respectively | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if the patterns link should go by way of the glossary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't feel up to writing that glossary entry. I'll leave that to your hypothetical future PR. |
||
acceptable `terms`, and `privateArgs`. `upgradability` can be `none` (the contract is not | ||
upgradable), `canUpgrade` (this code can perform an upgrade), or `canBeUpgraded` (the contract | ||
stores kinds durably such that the next version can upgrade). | ||
|
||
<<< @/../snippets/zoe/src/02b-state-durable.js#export-start | ||
|
||
### 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: | ||
<a id="baggage"></a> | ||
|
||
The 3rd argument, `baggage`, of the `start` function is a `MapStore` that is saved by the kernel | ||
across restarts of the contract. It provides a way to preserve state and behavior of objects between | ||
incarnations in a way that also maintains the identity of objects as seen from other [vats](/glossary/#vat). | ||
|
||
```js | ||
let rooms; | ||
|
@@ -113,7 +127,8 @@ When the contract instance is restarted, its [vat](../js-programming/#vats-the-u | |
|
||
### Kinds | ||
|
||
Use `zone.exoClass()` to define state and methods of kinds of durable objects such as `Room`: | ||
Use [`zone.exoClass()`](./contract-details.md#durable-objects) to define state and methods of kinds | ||
of durable objects such as `Room`: | ||
|
||
<<< @/../snippets/zoe/src/02b-state-durable.js#exoclass | ||
|
||
|
@@ -141,8 +156,9 @@ const makeRoom = zone.exoClass('Room', RoomI, id => ({ id, 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> | ||
The interface guard also needs updating. <small>_[The Durable | ||
objects](./contract-details.md#guards-defensive-methods) section has more on interface | ||
guards._</small> | ||
|
||
```js | ||
const RoomI = M.interface('Room', { | ||
|
@@ -175,20 +191,6 @@ Define all exo classes/kits before any incoming method calls from other vats -- | |
|
||
::: | ||
|
||
### Baggage | ||
|
||
baggage is a MapStore that provides a way to preserve the state and behavior of objects between [smart contract upgrades](/guides/zoe/contract-upgrade) in a way that preserves the identity of objects as seen from other [vats](#vat). In the provided contract, baggage is used to ensure that the state of various components is maintained even after the contract is upgraded. | ||
|
||
```js | ||
export const start = async (zcf, privateArgs, baggage) => { | ||
// ... | ||
const { accountsStorageNode } = await provideAll(baggage, { | ||
accountsStorageNode: () => E(storageNode).makeChildNode('accounts') | ||
}); | ||
// ... | ||
}; | ||
``` | ||
|
||
### Exo | ||
|
||
An Exo object is an exposed Remotable object with methods (aka a [`Far`](/guides/js-programming/far) object) which is | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made a patterns cheat-sheet at https://docs.agoric.com/reference/zoe-api/zoe-contract-facet.html#proposal-shapes, but that context is specialized to proposal shapes. hm.
Perhaps using glossary entries for Pattern and Shape would help. Perhaps I should do that in a follow-up PR.