Skip to content
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

create addVat() device #24

Closed
warner opened this issue Apr 6, 2019 · 12 comments
Closed

create addVat() device #24

warner opened this issue Apr 6, 2019 · 12 comments
Assignees
Labels
SwingSet package: SwingSet

Comments

@warner
Copy link
Member

warner commented Apr 6, 2019

Some Vats should have the ability to create new Vats. We should probably treat this authority as a "device", to provide it selectively to certain initial vats. This might also make it easier to provide certain authority over the new Vat: termination, migration, upgrade, and debugging.

warner referenced this issue in warner/SwingSet Sep 19, 2019
.. as the index for kernel state data structures, instead of user-provided
vatName/deviceName strings. This will make it easier to use key/value state
storage (#144), since the keys will be more uniform (and shorter, and not
vulnerable to people using the separator string in their vat name).

Also it will make adding dynamic Vats easier (#19), as we'll just increment a
counter instead of asking the user to discover a unique name.

closes #146
warner referenced this issue in warner/SwingSet Sep 19, 2019
.. as the index for kernel state data structures, instead of user-provided
vatName/deviceName strings. This will make it easier to use key/value state
storage (#144), since the keys will be more uniform (and shorter, and not
vulnerable to people using the separator string in their vat name).

Also it will make adding dynamic Vats easier (#19), as we'll just increment a
counter instead of asking the user to discover a unique name.

closes #146
warner referenced this issue in Agoric/SwingSet Sep 19, 2019
.. as the index for kernel state data structures, instead of user-provided
vatName/deviceName strings. This will make it easier to use key/value state
storage (#144), since the keys will be more uniform (and shorter, and not
vulnerable to people using the separator string in their vat name). They look
like "vNN" and "dNN", where NN is a positive integer.

This will also make adding dynamic Vats easier (#19), as we'll just increment
a counter instead of asking the user to invent a unique name.

closes #146
@Chris-Hibbert
Copy link
Contributor

I'm working on design of a vat spawning device. Some assumptions:

  • The contract host (somewhat trimmed down) and Zoe will be the primary clients
  • There will be a wrapping vat for the device, which will have exclusive access to device nodes and export objects useable to vats. I suspect that only Zoe and contract host will have access to this vat.
  • There needs to be a capability that creates new vats on the device, and wrapped by the vat, as well as an owner facet created for the new vat that can shut it down. I presume there will be other record-keeping or management tools, but I don't know what those look like yet.

kernel.js has addGenesisVat(), which insists that genesis vats can only be created at startup time, but clearly anticipates that others will be added later. We need a function here that adds vats at run-time. That function would be made accessible to controller.js, which would wrap it and include it as part of the controller API, which buildSwingSet() could hand to the spawner device as an endowment.

I don't know how the spawner gets the source code. The genesis vats are all read statically from local files. I think it makes more sense to pass in a source-text string, but I'm willing to be convinced otherwise. Dean has talked about a large string vat/device so we don't have to pass strings all over the place, but that doesn't exist yet.

@erights
Copy link
Member

erights commented Nov 16, 2019

I don't know how the spawner gets the source code.

From the app programmer's high level pov, the answer will be in terms of a loader/importer (defining an import namespace) and a module name (to be interpreted by that loader). No one loader is privileged, and so no one import namespace is. However, the normal app programmer will have a sensible default they won't think much about. Each loader is a distinct capability. Resource linkage integrity is, essentially, ensuring that remote loaders that are trying to be in agreement actually are, for example, by a hash of the full linkage graph.

At a low level, this would all turn into an evaluable string only on engines like v8 where our security kernel is about safe evaluation of evaluable strings, and modules translate into those. On XS's SES, modules are as fundamental as evaluable strings, so the high level module-oriented answer must turn into a low level module-oriented answer. Figuring this out likely depends on settling with Moddable what our full safe-module API is.

Despite all these qualifications, for what you're doing now, start with evaluable strings. @dtribble's thoughts about string devices are directly relevant to this. Perhaps the reification of a string as a string device can be unified with the idea of an installation as a reification of spawnable/runnable code. An instatiable fully-linked static module linkage graph might plausible be reified into the same notion of an installation.

Many open question here. Attn @michaelfig @jfparadis.

@michaelfig
Copy link
Member

@Chris-Hibbert, please look at Zoe's and the contractHost's install methods. Notably, the source and moduleFormat arguments should be supported. For the spawner, I suggest to default to (and only support) moduleFormat = 'getExports'. That getExports format is implemented in Zoe and the contractHost using @agoric/evaluate. You will need to do the same.

The @agoric/bundle-source package generates source bundles. It's already used in SwingSet for the genesis vats, and it will be how clients transform an ESM directory into a bundle for uploading to the spawner.

@erights
Copy link
Member

erights commented Nov 19, 2019

Hi @michaelfig curious about your sense of how we transition from this to using our safe module system? Is there something we should do or not do when using @agoric/bundle-source that would help ease that transition?

@michaelfig
Copy link
Member

As long as we always pass the source and moduleFormat parameters to the things we're sending module trees to, we are free to change the representation to one that better supports safe modules. Currently, Zoe, contractHost, and (soon-to-be) spawner are the only implementations of the code that extracts a bundle into a module namespace. When it comes time to do safe modules, we'll probably create a common package that all extractors can use, to couple with @agoric/bundle-source.

@warner
Copy link
Member Author

warner commented Nov 22, 2019

A few notes from conversations with Chris:

  • Developers start with a directory full of source code, which has various imports and tildot, and a clear entrypoint (index.js). They run some CLI command, which gets to read and process this directory, and builds a "code artifact" that is then delivered to the SwingSet machine (either the chain or a local solo machine). That artifact is then used to build the new Vat.

  • We get to (must) decide on the specification of the code artifact that gets passed from the developer's machine to the chain-side API for creating new vats.

  • I think the simplest starting point is: a string, containing a javascript expression, which evaluates to a function, which when invoked (with no arguments) returns an object with a .default property, which is a function that takes an E argument and returns a (vat-style) "root object". The evaluation environment will include require in scope, and this require will honor three "platform module" names: @agoric/evaluate, @agoric/harden, and @agoric/nat. The string is not allowed to use tildot.

  • If we start there, the developer-side CLI tool is responsible for using rollup to interpolate all imports (except for those three) into a single string, and transforming tildot into E() calls at the same time. I think of this as "rendering" the source tree into the code artifact (you could also think of it as a partial linker: resolving most imports but leaving the platform-specific ones for later).

  • On the SwingSet side, we need an evaluator that accepts expressions with the above shape, run with an endowment that provides a require that knows about exactly those three module names. This evaluator does not need to know about tildot or perform tildottish transforms. The SwingSet controller already has an evaluator which behaves this way (it might also know about tildot), built from SES or built from require('@agoric/evaluate') (in the --without-SES case).

  • As a starting point, we can have the Controller be responsible for building this evaluator and providing it to the kernel (by adding it to kernelEndowments, which are passed as arguments into the buildKernel() call). This is easier to implement right now, but not ideal, since in general we want to reduce the amount of code in the Controller (in the XS world, much of the Controller must be written in C, or at least not in normal confined javascript). I believe the kernel has enough authority to build this evaluator itself, especially since it gets to do import xxx from '@agoric/evaluator', so I'd like to see if we can remove the Controller's awareness of this evaluator.

Other options for this "code artifact" specification include:

  • Defer the Tildot Transformation to the kernel-side evaluator. I suspect this is easy for one of the SES/without-SES cases, and harder for the other, but I have no idea which might be the easier case.
  • Deliver a collection of modules rather than a single pre-rendered string. This might fit better into the new safe-modules world. It might also reduce the size of the artifact that must be transferred (we could use an interactive protocol in which the receiver can say "oh I already have modules with those hashes, only send me the new ones"). It would also open up more late-binding options, so a developer could say "import the most recent reviewed+approved version of XYZ", and the receiver would decide exactly how to resolve that import just before the vat is created.
  • We currently have static ("genesis") Vats defined to return a setup function, which takes a syscall object and returns a dispatch object. The code of the setup function does not have to implement/obey fine-grained object-capability -style confinement, but we usually expect it to use the liveSlots helper function to implement that layer, and then register an initial "root object" at a well-known reference ID (o+0, the first exported object, usually as the return value of a function named build). That sounds like too much authority for a dynamic vat, so in the proposed specification above, the code artifact only supplies a constructor for their root object.
  • Our build function currently accepts an E argument in lieu of doing tildot. We'd like to either get rid of that entirely (using Handled Promises instead), or have it be imported from something (maybe @agoric/eventual-send). That will require changes to the way require works (if we add a fourth platform module).

@warner
Copy link
Member Author

warner commented Nov 23, 2019

More notes:

We think there will be a vatAdminVat and a vatAdminDevice. Both should be "built-in", meaning that the host doesn't have to mention them in the config object passed into createVatController.

Each dynamic Vat has two kinds of identifiers. The first is an integer allocated by the vatAdminVat, call it an admin ID. The second is an integer allocated by the kernel, used in the kernel state, and known as the vatID.

The vatAdminDevice has a persistent table that maps from admin IDs to vatIDs. It exports a single object (o+0) which has methods like:

  • .create(adminID, code)
  • .terminate(adminID)
  • and maybe some get-status -type stuff, meter/keeper things, in the future

The vatAdminDevice has an endowment into the kernel which is used for creating new vats. When create() is called, this kernel-side endowment function must:

  • Allocate a new vatID
  • Evaluate the code string, to produce a function (named build or makeRoot) which takes an E argument and returns a new root object
  • Define a new function (named setup) which invokes liveSlots with that makeRoot function, and returns its result
  • Pass setup into a call to makeVatManager, which will build a syscall object, invoke setup(syscall, etc), and expect a dispatch object back
  • At this point the vat is constructed, but not wired to anyone else. The new Vat's liveSlots table maps from o+0 to the root object, but there is not yet an entry in the kernel's c-list for this Vat.
  • Now it must allocate a new kernel object ID (e.g. ko12), mark the "owner" as pointing to the new vatID, and add an export entry to the new Vat's c-list (vatID4: ko12 <-> o+0)
  • The endowment must add an entry to the device's c-list, granting the device access to the new root object as an import. This requires allocating an import ID (e.g. o-1) and updating the device's c-list (device3: ko12 <-> o-1).
  • Finally, the endowment must return this new import ID (o-1) to the device, as the return value of the endowment call. It also returns the new vatID.

The device code handling create() then updates a table mapping adminID to vatID, and sends a message to the vatAdminVat in response to the create(). This response message must include the adminID of the request, and a reference to the root object (by placing its import ID o-1 in the .slots portion of the arguments). When this message leaves the vatAdminDevice, it will be looked up in the device's c-list by the kernel, and then imported into the vatAdminVat's c-list on the way in. The admin vat's code can pass this object along to the actual caller.

The vatAdminVat has a create(code) method which:

  • allocates a new adminID
  • sends a create(code, adminID) message to the device and waits for a response
  • when the response arrives, it builds a vatAdmin object that remembers the adminID
  • finally it resolves the result promise to a record that includes both the root object and the vatAdmin object

Later, to terminate the vat, the caller can send a message to the vatAdmin object. This will use its private connection to the device to send a termination message (citing the adminID). The device will then map the adminID to the real kernel-side vatID, and use a separate endowment to terminate the vat.

(what actually happens when we terminate the vat is another large piece of work: the main question is how to cauterize the now-dangling references, and who is responsible for creating errors in response to messages sent on them. One thought is to have a special vat which just throws errors for any message it receives, and then change the routing of all dangling references to point at the error vat. This would keep the kernel out of the business of creating error messages. We might be able to use this trick for send-to-data errors too, which are currently built by the kernel.)

@Chris-Hibbert
Copy link
Contributor

IMG_20191122_155321
IMG_20191122_155308

@michaelfig
Copy link
Member

@warner If you use @agoric/evaluate for both SES and nonSES, tildot is supported kernel-side. IMO, that's where it should be done.

@michaelfig
Copy link
Member

Oh, and @agoric/eventual-send is a powerless module when used within @agoric/evaluate and @agoric/default-evaluate-options, so no special treatment is needed.

@warner warner transferred this issue from Agoric/SwingSet Dec 1, 2019
@warner warner added the SwingSet package: SwingSet label Dec 1, 2019
dckc pushed a commit to dckc/agoric-sdk that referenced this issue Dec 5, 2019
dckc pushed a commit to dckc/agoric-sdk that referenced this issue Dec 5, 2019
rename gallery demo markdown and put in a number of commands
dckc pushed a commit to dckc/agoric-sdk that referenced this issue Dec 5, 2019
…g a digit). This tests for that case and adds a digit if needed, and also errors if that still doesn't fix it. Eventually I'd like to change the actual algorithm but I don't understand the bit operations fully without diving in further. (Agoric#24)
@Chris-Hibbert
Copy link
Contributor

Chris-Hibbert commented Dec 17, 2019

Here are some more details on how the vat, kernel, and devices get tied together, and what functions rely on which others to get their work done. The main change from the previous description is that the vatAdmin Vat doesn't wait for a response from the device. It can construct the vat admin object immediately and return it with a promise for the root object.

The outer device has functions that allow the kernel to register createDynamicVat with the inner device.

The inner device remembers the mapping from adminID to vatID. It creates vats, terminates them, and can provide admin support. On creation, it only returns the new VatID to the vatAdminVat, and provides the root object in a follow-up reply. It has a function the kernel can use to notify it when vats have been created, but it doesn't have to register it, because the kernel will synthesize these calls. We expect a prompt reply to requests for statistics, so these will be returned immediately. Termination will be complicated

The vatAdminVat will be closely held and only provided to things like Zoe and the re-purposed contract host. It will have functionality for creating and adminstering (including termination) dynamic vats. When a new vat is requested, it will promptly return an admin interface and a promise for the root object. The root object promise will be resolved some time later, but messages can be sent to it immediately. The admin interface just wraps the vatID, and makes it possible to request info from the kernel and terminate the vat.

The kernel will have a method for creating new vats which will immediately return the new vat's ID. Once the new vat has been built, it will notify the vat creation device with a synthesized message passing the root object. The kernel will create the vat, then create the necessary entries in the kernel's tables so messages can be delivered.

Inner Device has
calls registerVatCreationSetter(setVatCreationFunction) at startup

  • map from adminID to vatID
  • setVatCreationFunction(createVatFn)
    remembers the vatCreationFunction provided by the kernel
  • notifyNewDynamicVat(rootObject, adminId)
    called by the kernel.
    calls notifyVatCreated to pass the rootObject to the requestor
  • create(adminId, code)
    return a vat ID
  • terminate(adminId)
  • getAdminInfo(adminId)
    return synchronously

Outer Device has

  • registerVatCreationFunction(fn)
    exported to kernel
    saves the kernel function that creates dynamic vats
  • registerVatCreationSetter
    exported to inner Vat
    Saves the inner vat's fn that sets the creation function

createVatFn is

  • vatId? createVatFunction(srcString, adminId)

vatAdminVat
map from adminId to root object
nextAdminId

  • createDynamicVat(sourceCode)
    Called by vat code to create new vat
    calls D(device).create(nextId, code)
    return a management object and promise for the new vat's root object
    the root object won't respond until the vat is created, but that
    should be invisible to callers.

    The management object wraps the adminID and has:

    • getAdminInfo(details?)
      return synchronously
    • terminate()
      return synchronously
      may take a while to cauterize everything
      First step is to tell the dynamic vat to stop responding
      details to be worked out. May not be pretty initially.
  • notifyVatCreated(rootObject, adminId)
    called back by the device
    links the callable object to the new vat's root object

Kernel provides

  • createDynamicVat(adminId, sourceCode)
    registered with the device before other vats & devices
    returns the next vatID
    creates a new vat
    creates liveSlot entry for the new device' root object
    when the vat is ready, synthesizes a call to the device notifyNewDynamicVat(rootObject, adminId)
  • getAdminInfo(adminId)
    synchronously returns info about the vat.

Calls registerVatCreationFunction to invoke the function that remembers createDynamicVat
After device has been initialized.

@Chris-Hibbert
Copy link
Contributor

We implemented something slightly different, so here's a corrected reprise:

The outer device has functions that allow the kernel to register createDynamicVat with the inner device.

The inner device creates vats, terminates them, and can provide admin support. On creation, it only returns the new VatID to the vatAdminVat, and provides the root object in a follow-up reply. We expect a prompt reply to requests for statistics, so these will be returned immediately. Termination will be complicated

The vatAdminVat will be closely held and only provided to things like Zoe and the re-purposed contract host. It will have functionality for creating and administering (including termination) dynamic vats. When a new vat is requested, it will promptly return an admin interface and a promise for the root object. The root object promise will be resolved some time later, but messages can be sent to it immediately. The admin interface just wraps the vatID, and makes it possible to request info from the kernel and terminate the vat.

The kernel will have a function for creating new vats which will immediately return the new vat's ID. This function will be registered with the device. Once a new vat has been built, the kernel will notify the vat creation vat with a synthesized message passing the root object. The kernel will create the vat, then create the necessary entries in the kernel's tables so messages can be delivered.

The inner device

  • calls registerVatCreationSetter(setVatCreationFunction) at startup
  • remembers functions provided by the kernel for creation, termination, and getting stats

The inner device has

  • setVatManagementFunctions(createFn, terminateFn, adminFn)
  • create(adminId, code) [return a vat ID]
  • terminate(adminId)
  • getAdminInfo(adminId) [return synchronously]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
SwingSet package: SwingSet
Projects
None yet
Development

No branches or pull requests

4 participants