Skip to content

Commit

Permalink
feat(vats): choose bootstrap behaviors by name
Browse files Browse the repository at this point in the history
Goal: allow genesis to specify exactly which functions
get what powers during bootstrap.

 - filter bootstrap powers based on templates
 - layer simBootstrapManifest on bootstrapManifest
 - log bootstrap behavior invocations with endowments
 - separate sim bootstrap behaviors
 - fold bootstrap-zoe-config.js into bootstrap-behaviors.js
 - move manifests with behaviors
 - fix .js in module specifier
  • Loading branch information
dckc authored and michaelfig committed Jan 16, 2022
1 parent 8058404 commit 13627b2
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 138 deletions.
73 changes: 73 additions & 0 deletions packages/vats/src/bootstrap-behaviors-sim.js
Original file line number Diff line number Diff line change
@@ -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<string, unknown> },
* vats: {
* vattp: VattpVat,
* comms: CommsVatRoot,
* },
* workspace: Record<string, ERef<unknown>>,
* }} 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';
75 changes: 75 additions & 0 deletions packages/vats/src/bootstrap-behaviors.js
Original file line number Diff line number Diff line change
@@ -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<string, ERef<any>>,
* }} powers
*
* @typedef {ERef<ReturnType<import('./vat-zoe').buildRootObject>>} 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 };
146 changes: 48 additions & 98 deletions packages/vats/src/bootstrap-core.js
Original file line number Diff line number Diff line change
@@ -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<string, unknown> },
* vats: {
* vattp: VattpVat,
* comms: CommsVatRoot,
* },
* workspace: Record<string, ERef<unknown>>,
* }} 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<string, ERef<any>>,
* }} powers
*
* @typedef {ERef<ReturnType<import('./vat-zoe').buildRootObject>>} 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.
Expand Down Expand Up @@ -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.
Expand All @@ -150,7 +85,7 @@ const bootstrapSteps = [connectVattpWithMailbox, installSimEgress, buildZoe];
* argv: Record<string, unknown>,
* }} vatParameters
*/
export function buildRootObject(vatPowers, vatParameters) {
const buildRootObject = (vatPowers, vatParameters) => {
const workspace = makePromiseSpace();

return Far('bootstrap', {
Expand All @@ -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 };
35 changes: 0 additions & 35 deletions packages/vats/src/bootstrap-zoe-config.js

This file was deleted.

6 changes: 1 addition & 5 deletions packages/vats/src/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
11 changes: 11 additions & 0 deletions packages/vats/test/test-extract.js
Original file line number Diff line number Diff line change
@@ -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 } });
});

0 comments on commit 13627b2

Please sign in to comment.