diff --git a/packages/vats/src/bootstrap-behaviors-sim.js b/packages/vats/src/bootstrap-behaviors-sim.js new file mode 100644 index 00000000000..fe6490247fa --- /dev/null +++ b/packages/vats/src/bootstrap-behaviors-sim.js @@ -0,0 +1,73 @@ +// @ts-check +import { E, Far } from '@agoric/far'; +import { makeNotifierKit } from '@agoric/notifier'; +import { bootstrapManifest } from './bootstrap-behaviors.js'; + +export const simBootstrapManifest = harden({ + behaviors: { installSimEgress: true, ...bootstrapManifest.behaviors }, + endowments: { + installSimEgress: { + vatParameters: { argv: { hardcodedClientAddresses: true } }, + vats: { + vattp: true, + comms: true, + }, + workspace: true, + }, + ...bootstrapManifest.endowments, + }, +}); + +/** + * @param {{ + * vatParameters: { argv: Record }, + * vats: { + * vattp: VattpVat, + * comms: CommsVatRoot, + * }, + * workspace: Record>, + * }} powers + * + * @typedef {{ getChainBundle: () => unknown }} ChainBundler + */ +const installSimEgress = async ({ vatParameters, vats, workspace }) => { + const PROVISIONER_INDEX = 1; + + const { argv } = vatParameters; + const addRemote = async addr => { + const { transmitter, setReceiver } = await E(vats.vattp).addRemote(addr); + await E(vats.comms).addRemote(addr, transmitter, setReceiver); + }; + + // TODO: chainProvider per address + let bundle = harden({ + echoer: Far('echoObj', { echo: message => message }), + // TODO: echo: Far('echoFn', message => message), + }); + const { notifier, updater } = makeNotifierKit(bundle); + + const chainProvider = Far('chainProvider', { + getChainBundle: () => notifier.getUpdateSince().then(({ value }) => value), + getChainBundleNotifier: () => notifier, + }); + + await Promise.all( + /** @type { string[] } */ (argv.hardcodedClientAddresses).map( + async addr => { + await addRemote(addr); + await E(vats.comms).addEgress(addr, PROVISIONER_INDEX, chainProvider); + }, + ), + ); + + workspace.allClients = harden({ + assign: newProperties => { + bundle = { ...bundle, ...newProperties }; + updater.updateState(bundle); + }, + }); +}; + +harden({ installSimEgress }); +export { installSimEgress }; +export * from './bootstrap-behaviors.js'; diff --git a/packages/vats/src/bootstrap-behaviors.js b/packages/vats/src/bootstrap-behaviors.js new file mode 100644 index 00000000000..33ec332a4a6 --- /dev/null +++ b/packages/vats/src/bootstrap-behaviors.js @@ -0,0 +1,75 @@ +// @ts-check +import { E } from '@agoric/far'; +import { AssetKind } from '@agoric/ertp'; + +// TODO: phase out ./issuers.js +export const CENTRAL_ISSUER_NAME = 'RUN'; + +/** @type { FeeIssuerConfig } */ +export const feeIssuerConfig = { + name: CENTRAL_ISSUER_NAME, + assetKind: AssetKind.NAT, + displayInfo: { decimalPlaces: 6, assetKind: AssetKind.NAT }, +}; + +export const bootstrapManifest = harden({ + behaviors: { + connectVattpWithMailbox: true, + buildZoe: true, + }, + endowments: { + connectVattpWithMailbox: { + vatPowers: { D: true }, + vats: { vattp: true }, + devices: { mailbox: true }, + }, + buildZoe: { + vats: { vatAdmin: true }, + devices: { vatAdmin: true }, + workspace: true, + }, + }, +}); + +/** + * @param {{ + * vatPowers: { D: EProxy }, // D type is approximate + * vats: { vattp: VattpVat }, + * devices: { mailbox: MailboxDevice }, + * }} powers + */ +const connectVattpWithMailbox = ({ + vatPowers: { D }, + vats: { vattp }, + devices: { mailbox }, +}) => { + D(mailbox).registerInboundHandler(vattp); + return E(vattp).registerMailboxDevice(mailbox); +}; + +/** + * @param {{ + * vats: { vatAdmin: VatAdminVat }, + * devices: { vatAdmin: unknown }, + * workspace: Record>, + * }} powers + * + * @typedef {ERef>} ZoeVat + */ +const buildZoe = async ({ vats, devices, workspace }) => { + // TODO: what else do we need vatAdminSvc for? can we let it go out of scope? + const vatAdminSvc = E(vats.vatAdmin).createVatAdminService(devices.vatAdmin); + + /** @type {{ root: ZoeVat }} */ + const { root } = await E(vatAdminSvc).createVatByName('zoe'); + const { zoeService: zoe, feeMintAccess: _2 } = await E(root).buildZoe( + vatAdminSvc, + feeIssuerConfig, + ); + + workspace.zoe = zoe; + E(workspace.allClients).assign({ zoe }); +}; + +harden({ connectVattpWithMailbox, buildZoe }); +export { connectVattpWithMailbox, buildZoe }; diff --git a/packages/vats/src/bootstrap-core.js b/packages/vats/src/bootstrap-core.js index ae1aac716c8..3ca0f92ab25 100644 --- a/packages/vats/src/bootstrap-core.js +++ b/packages/vats/src/bootstrap-core.js @@ -1,99 +1,12 @@ // @ts-check -import { E, Far } from '@agoric/far'; -import { makeNotifierKit } from '@agoric/notifier'; +import { Far } from '@agoric/far'; import { makePromiseKit } from '@agoric/promise-kit'; +// TODO: choose sim behaviors based on runtime config +import * as behaviors from './bootstrap-behaviors-sim.js'; +import { simBootstrapManifest } from './bootstrap-behaviors-sim.js'; -import { feeIssuerConfig } from './bootstrap-zoe-config'; - -/** - * @param {{ - * vatPowers: { D: EProxy }, // D type is approximate - * vats: { vattp: VattpVat }, - * devices: { mailbox: MailboxDevice }, - * }} powers - */ -const connectVattpWithMailbox = ({ - vatPowers: { D }, - vats: { vattp }, - devices: { mailbox }, -}) => { - D(mailbox).registerInboundHandler(vattp); - return E(vattp).registerMailboxDevice(mailbox); -}; - -/** - * @param {{ - * vatParameters: { argv: Record }, - * vats: { - * vattp: VattpVat, - * comms: CommsVatRoot, - * }, - * workspace: Record>, - * }} powers - * - * @typedef {{ getChainBundle: () => unknown }} ChainBundler - */ -const installSimEgress = async ({ vatParameters, vats, workspace }) => { - const PROVISIONER_INDEX = 1; - - const { argv } = vatParameters; - const addRemote = async addr => { - const { transmitter, setReceiver } = await E(vats.vattp).addRemote(addr); - await E(vats.comms).addRemote(addr, transmitter, setReceiver); - }; - - // TODO: chainProvider per address - let bundle = harden({ - echoer: Far('echoObj', { echo: message => message }), - // TODO: echo: Far('echoFn', message => message), - }); - const { notifier, updater } = makeNotifierKit(bundle); - - const chainProvider = Far('chainProvider', { - getChainBundle: () => notifier.getUpdateSince().then(({ value }) => value), - getChainBundleNotifier: () => notifier, - }); - - await Promise.all( - /** @type { string[] } */ (argv.hardcodedClientAddresses).map( - async addr => { - await addRemote(addr); - await E(vats.comms).addEgress(addr, PROVISIONER_INDEX, chainProvider); - }, - ), - ); - - workspace.allClients = harden({ - assign: newProperties => { - bundle = { ...bundle, ...newProperties }; - updater.updateState(bundle); - }, - }); -}; - -/** - * @param {{ - * vats: { vatAdmin: VatAdminVat }, - * devices: { vatAdmin: unknown }, - * workspace: Record>, - * }} powers - * - * @typedef {ERef>} ZoeVat - */ -const buildZoe = async ({ vats, devices, workspace }) => { - // TODO: what else do we need vatAdminSvc for? can we let it go out of scope? - const vatAdminSvc = E(vats.vatAdmin).createVatAdminService(devices.vatAdmin); - - /** @type {{ root: ZoeVat }} */ - const { root } = await E(vatAdminSvc).createVatByName('zoe'); - const { zoeService: zoe, feeMintAccess: _2 } = await E(root).buildZoe( - vatAdminSvc, - feeIssuerConfig, - ); - - workspace.zoe = zoe; - E(workspace.allClients).assign({ zoe }); -}; +const { entries, fromEntries, keys } = Object; +const { details: X, quote: q } = assert; /** * Make an object `s` where every `s.name` is a promise and setting `s.name = v` resolves it. @@ -138,7 +51,29 @@ const makePromiseSpace = () => { return space; }; -const bootstrapSteps = [connectVattpWithMailbox, installSimEgress, buildZoe]; +/** + * @param {unknown} template + * @param {unknown} specimen + */ +const extract = (template, specimen) => { + if (template === true) { + return specimen; + } else if (typeof template === 'object' && template !== null) { + if (typeof specimen !== 'object' || specimen === null) { + assert.fail(X`object template requires object specimen, not ${specimen}`); + } + return harden( + fromEntries( + entries(template).map(([propName, subTemplate]) => [ + propName, + extract(subTemplate, specimen[propName]), + ]), + ), + ); + } else { + assert.fail(X`unexpected template: ${q(template)}`); + } +}; /** * Build root object of the bootstrap vat. @@ -150,7 +85,7 @@ const bootstrapSteps = [connectVattpWithMailbox, installSimEgress, buildZoe]; * argv: Record, * }} vatParameters */ -export function buildRootObject(vatPowers, vatParameters) { +const buildRootObject = (vatPowers, vatParameters) => { const workspace = makePromiseSpace(); return Far('bootstrap', { @@ -162,9 +97,24 @@ export function buildRootObject(vatPowers, vatParameters) { */ bootstrap: (vats, devices) => Promise.all( - bootstrapSteps.map(step => - step({ vatPowers, vatParameters, vats, devices, workspace }), + // TODO: choose simBootstrapManifest based on runtime config + keys(simBootstrapManifest.behaviors).map(name => + Promise.resolve().then(() => { + const permit = simBootstrapManifest.endowments[name]; + const endowments = extract(permit, { + vatPowers, + vatParameters, + vats, + devices, + workspace, + }); + console.info(`bootstrap: ${name}(${q(permit)})`); + return behaviors[name](endowments); + }), ), ), }); -} +}; + +harden({ buildRootObject, extract }); +export { buildRootObject, extract }; diff --git a/packages/vats/src/bootstrap-zoe-config.js b/packages/vats/src/bootstrap-zoe-config.js deleted file mode 100644 index 9821363aebc..00000000000 --- a/packages/vats/src/bootstrap-zoe-config.js +++ /dev/null @@ -1,35 +0,0 @@ -import { AssetKind } from '@agoric/ertp'; - -// TODO: phase out ./issuers.js -export const CENTRAL_ISSUER_NAME = 'RUN'; - -export const feeIssuerConfig = { - name: CENTRAL_ISSUER_NAME, - assetKind: AssetKind.NAT, - displayInfo: { decimalPlaces: 6, assetKind: AssetKind.NAT }, - initialFunds: 1_000_000_000_000_000_000n, // ISSUE: we can't make RUN out of thin air -}; - -const MINUTE = 60n * 1000n; // in milliseconds -const DAY = 24n * 60n * MINUTE; -export const zoeFeesConfig = chainTimerServiceP => ({ - getPublicFacetFee: 50n, - installFee: 65_000n, - startInstanceFee: 5_000_000n, - offerFee: 65_000n, - timeAuthority: chainTimerServiceP, - lowFee: 500_000n, - highFee: 5_000_000n, - shortExp: 5n * MINUTE, - longExp: 1n * DAY, -}); - -export const meteringConfig = { - incrementBy: 25_000_000n, - initial: 50_000_000n, - threshold: 25_000_000n, - price: { - feeNumerator: 1n, - computronDenominator: 1n, // default is just one-to-one - }, -}; diff --git a/packages/vats/src/bootstrap.js b/packages/vats/src/bootstrap.js index 3f3efe2a19b..aa8fefb2a05 100644 --- a/packages/vats/src/bootstrap.js +++ b/packages/vats/src/bootstrap.js @@ -26,11 +26,7 @@ import { fromCosmosIssuerEntries, BLD_ISSUER_ENTRY, } from './issuers'; -import { - feeIssuerConfig, - zoeFeesConfig, - meteringConfig, -} from './bootstrap-zoe-config'; +import { feeIssuerConfig } from './bootstrap-behaviors.js'; const { multiply, floorDivide } = natSafeMath; diff --git a/packages/vats/test/test-extract.js b/packages/vats/test/test-extract.js new file mode 100644 index 00000000000..3b6ea336c55 --- /dev/null +++ b/packages/vats/test/test-extract.js @@ -0,0 +1,11 @@ +import '@agoric/install-ses'; +import test from 'ava'; + +import { extract } from '../src/bootstrap-core.js'; + +test('extract picks from specimen based on template', t => { + const specimen = { a: 1, b: { c: 2 } }; + const template = { b: { c: true } }; + const actual = extract(template, specimen); + t.deepEqual(actual, { b: { c: 2 } }); +});