diff --git a/packages/SwingSet/src/kernel/liveSlots.js b/packages/SwingSet/src/kernel/liveSlots.js index f0f0fbc5384..72ad79493e6 100644 --- a/packages/SwingSet/src/kernel/liveSlots.js +++ b/packages/SwingSet/src/kernel/liveSlots.js @@ -10,6 +10,7 @@ import { assert, details as X } from '@agoric/assert'; import { isPromise } from '@agoric/promise-kit'; import { insistVatType, makeVatSlot, parseVatSlot } from '../parseVatSlots'; import { insistCapData } from '../capdata'; +import { insistMessage } from '../message'; import { makeVirtualObjectManager } from './virtualObjectManager'; const DEFAULT_VIRTUAL_OBJECT_CACHE_SIZE = 3; // XXX ridiculously small value to force churn for testing @@ -764,13 +765,37 @@ function build( const rootObject = buildRootObject(harden(vpow), harden(vatParameters)); assert.equal(passStyleOf(rootObject), REMOTE_STYLE); - const rootSlot = makeVatSlot('object', true, 0n); + const rootSlot = makeVatSlot('object', true, BigInt(0)); valToSlot.set(rootObject, rootSlot); slotToVal.set(rootSlot, new WeakRef(rootObject)); exported.add(rootObject); } - const dispatch = harden({ deliver, notify, dropExports }); + function dispatch(vatDeliveryObject) { + const [type, ...args] = vatDeliveryObject; + switch (type) { + case 'message': { + const [targetSlot, msg] = args; + insistMessage(msg); + deliver(targetSlot, msg.method, msg.args, msg.result); + break; + } + case 'notify': { + const [resolutions] = args; + notify(resolutions); + break; + } + case 'dropExports': { + const [vrefs] = args; + dropExports(vrefs); + break; + } + default: + assert.fail(X`unknown delivery type ${type}`); + } + } + harden(dispatch); + return harden({ vatGlobals, setBuildRootObject, dispatch, m }); } diff --git a/packages/SwingSet/src/kernel/vatManager/deliver.js b/packages/SwingSet/src/kernel/vatManager/deliver.js index 3113bf8eac6..d4ba6013f5f 100644 --- a/packages/SwingSet/src/kernel/vatManager/deliver.js +++ b/packages/SwingSet/src/kernel/vatManager/deliver.js @@ -1,5 +1,14 @@ -import { assert, details as X } from '@agoric/assert'; -import { insistMessage } from '../../message'; +/** @type { (delivery: VatDeliveryObject, prefix: string) => string } */ +function summarizeDelivery(vatDeliveryObject, prefix = 'vat') { + const [type, ...args] = vatDeliveryObject; + if (type === 'message') { + const [targetSlot, msg] = args[0]; + return `${prefix}[${targetSlot}].${msg.method} dispatch failed`; + } + return `${prefix}.${type} failed`; +} +harden(summarizeDelivery); +export { summarizeDelivery }; export function makeDeliver(tools, dispatch) { const { @@ -44,11 +53,25 @@ export function makeDeliver(tools, dispatch) { // TODO: accumulate used.allocate and used.compute into vatStats } - async function doProcess(dispatchRecord, errmsg) { - const dispatchOp = dispatchRecord[0]; - const dispatchArgs = dispatchRecord.slice(1); - transcriptManager.startDispatch(dispatchRecord); - await runAndWait(() => dispatch[dispatchOp](...dispatchArgs), errmsg); + // vatDeliveryObject is one of: + // ['message', target, msg] + // target is vref + // msg is: { method, args (capdata), result } + // ['notify', resolutions] + // resolutions is an array of triplets: [vpid, rejected, value] + // vpid is the id of the primary promise being resolved + // rejected is a boolean flag indicating if vpid is being fulfilled or rejected + // value is capdata describing the value the promise is being resolved to + // The first entry in the resolutions array is the primary promise being + // resolved, while the remainder (if any) are collateral promises it + // references whose resolution was newly discovered at the time the + // notification delivery was being generated + // ['dropExports', vrefs] + + async function deliver(vatDeliveryObject) { + const errmsg = summarizeDelivery(vatDeliveryObject, `vat[${vatID}]`); + transcriptManager.startDispatch(vatDeliveryObject); + await runAndWait(() => dispatch(vatDeliveryObject), errmsg); stopGlobalMeter(); let status = ['ok', null, null]; @@ -75,59 +98,13 @@ export function makeDeliver(tools, dispatch) { return status; } - async function deliverOneMessage(targetSlot, msg) { - insistMessage(msg); - const errmsg = `vat[${vatID}][${targetSlot}].${msg.method} dispatch failed`; - return doProcess( - ['deliver', targetSlot, msg.method, msg.args, msg.result], - errmsg, - ); - } - - async function deliverOneNotification(resolutions) { - const errmsg = `vat[${vatID}].notify failed`; - return doProcess(['notify', resolutions], errmsg); - } - - async function deliverOneDropExports(vrefs) { - const errmsg = `vat[${vatID}].dropExports failed`; - return doProcess(['dropExports', vrefs], errmsg); - } - - // vatDeliverObject is: - // ['message', target, msg] - // target is vid - // msg is: { method, args (capdata), result } - // ['notify', resolutions] - // resolutions is an array of triplets: [vpid, rejected, value] - // vpid is the id of the primary promise being resolved - // rejected is a boolean flag indicating if vpid is being fulfilled or rejected - // value is capdata describing the value the promise is being resolved to - // The first entry in the resolutions array is the primary promise being - // resolved, while the remainder (if any) are collateral promises it - // references whose resolution was newly discovered at the time the - // notification delivery was being generated - async function deliver(vatDeliverObject) { - const [type, ...args] = vatDeliverObject; - switch (type) { - case 'message': - return deliverOneMessage(...args); - case 'notify': - return deliverOneNotification(...args); - case 'dropExports': - return deliverOneDropExports(...args); - default: - assert.fail(X`unknown delivery type ${type}`); - } - } - async function replayTranscript() { transcriptManager.startReplay(); for (const t of vatKeeper.getTranscript()) { transcriptManager.checkReplayError(); transcriptManager.startReplayDelivery(t.syscalls); // eslint-disable-next-line no-await-in-loop - await doProcess(t.d, null); + await deliver(t.d); } transcriptManager.checkReplayError(); transcriptManager.finishReplay(); diff --git a/packages/SwingSet/src/kernel/vatManager/manager-local.js b/packages/SwingSet/src/kernel/vatManager/manager-local.js index 92a2b893e12..a65d16ba8a0 100644 --- a/packages/SwingSet/src/kernel/vatManager/manager-local.js +++ b/packages/SwingSet/src/kernel/vatManager/manager-local.js @@ -32,9 +32,10 @@ export function makeLocalVatManagerFactory(tools) { const transcriptManager = makeTranscriptManager(vatKeeper, vatID); const { syscall, setVatSyscallHandler } = createSyscall(transcriptManager); function finish(dispatch, meterRecord) { - assert( - dispatch && dispatch.deliver, - `vat failed to return a 'dispatch' with .deliver: ${dispatch}`, + assert.equal( + typeof dispatch, + 'function', + `vat failed to return a 'dispatch' function: ${dispatch}`, ); const { deliver, replayTranscript } = makeDeliver( { diff --git a/packages/SwingSet/src/kernel/vatManager/supervisor-nodeworker.js b/packages/SwingSet/src/kernel/vatManager/supervisor-nodeworker.js index 8317b02b86e..8c599950a6e 100644 --- a/packages/SwingSet/src/kernel/vatManager/supervisor-nodeworker.js +++ b/packages/SwingSet/src/kernel/vatManager/supervisor-nodeworker.js @@ -10,6 +10,7 @@ import { makeMarshal } from '@agoric/marshal'; import { WeakRef, FinalizationRegistry } from '../../weakref'; import { waitUntilQuiescent } from '../../waitUntilQuiescent'; import { makeLiveSlots } from '../liveSlots'; +import { summarizeDelivery } from './deliver'; // eslint-disable-next-line no-unused-vars function workerLog(first, ...args) { @@ -41,34 +42,15 @@ function sendUplink(msg) { let dispatch; -async function doProcess(dispatchRecord, errmsg) { - const dispatchOp = dispatchRecord[0]; - const dispatchArgs = dispatchRecord.slice(1); +async function deliver(vatDeliveryObject) { workerLog(`runAndWait`); - await runAndWait(() => dispatch[dispatchOp](...dispatchArgs), errmsg); + const errmsg = summarizeDelivery(vatDeliveryObject); + await runAndWait(() => dispatch(vatDeliveryObject), errmsg); workerLog(`doProcess done`); const vatDeliveryResults = harden(['ok']); return vatDeliveryResults; } -function doMessage(targetSlot, msg) { - const errmsg = `vat[${targetSlot}].${msg.method} dispatch failed`; - return doProcess( - ['deliver', targetSlot, msg.method, msg.args, msg.result], - errmsg, - ); -} - -function doNotify(resolutions) { - const errmsg = `vat.notify failed`; - return doProcess(['notify', resolutions], errmsg); -} - -function doDropExports(vrefs) { - const errmsg = `vat.doDropExport failed`; - return doProcess(['dropExports', vrefs], errmsg); -} - parentPort.on('message', ([type, ...margs]) => { workerLog(`received`, type); if (type === 'start') { @@ -143,22 +125,10 @@ parentPort.on('message', ([type, ...margs]) => { workerLog(`error: deliver before dispatchReady`); return; } - const [[dtype, ...dargs]] = margs; - if (dtype === 'message') { - doMessage(...dargs).then(vatDeliveryResults => - sendUplink(['deliverDone', vatDeliveryResults]), - ); - } else if (dtype === 'notify') { - doNotify(...dargs).then(vatDeliveryResults => - sendUplink(['deliverDone', vatDeliveryResults]), - ); - } else if (dtype === 'dropExports') { - doDropExports(...dargs).then(vatDeliveryResults => - sendUplink(['deliverDone', vatDeliveryResults]), - ); - } else { - assert.fail(X`bad delivery type ${dtype}`); - } + const [vatDeliveryObject] = margs; + deliver(vatDeliveryObject).then(vatDeliveryResults => + sendUplink(['deliverDone', vatDeliveryResults]), + ); } else { workerLog(`unrecognized downlink message ${type}`); } diff --git a/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-node.js b/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-node.js index 3849bc0cd84..a2c37203fda 100644 --- a/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-node.js +++ b/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-node.js @@ -15,6 +15,7 @@ import { } from '../../netstring'; import { waitUntilQuiescent } from '../../waitUntilQuiescent'; import { makeLiveSlots } from '../liveSlots'; +import { summarizeDelivery } from './deliver'; // eslint-disable-next-line no-unused-vars function workerLog(first, ...args) { @@ -41,34 +42,15 @@ function runAndWait(f, errmsg) { let dispatch; -async function doProcess(dispatchRecord, errmsg) { - const dispatchOp = dispatchRecord[0]; - const dispatchArgs = dispatchRecord.slice(1); +async function deliver(vatDeliveryObject) { workerLog(`runAndWait`); - await runAndWait(() => dispatch[dispatchOp](...dispatchArgs), errmsg); + const errmsg = summarizeDelivery(vatDeliveryObject); + await runAndWait(() => dispatch(vatDeliveryObject), errmsg); workerLog(`doProcess done`); const vatDeliveryResults = harden(['ok']); return vatDeliveryResults; } -function doMessage(targetSlot, msg) { - const errmsg = `vat[${targetSlot}].${msg.method} dispatch failed`; - return doProcess( - ['deliver', targetSlot, msg.method, msg.args, msg.result], - errmsg, - ); -} - -function doNotify(resolutions) { - const errmsg = `vat.notify failed`; - return doProcess(['notify', resolutions], errmsg); -} - -function doDropExports(vrefs) { - const errmsg = `vat.doDropExport failed`; - return doProcess(['dropExports', vrefs], errmsg); -} - const toParent = arrayEncoderStream(); toParent .pipe(netstringEncoderStream()) @@ -163,22 +145,10 @@ fromParent.on('data', ([type, ...margs]) => { workerLog(`error: deliver before dispatchReady`); return; } - const [[dtype, ...dargs]] = margs; - if (dtype === 'message') { - doMessage(...dargs).then(vatDeliveryResults => - sendUplink(['deliverDone', vatDeliveryResults]), - ); - } else if (dtype === 'notify') { - doNotify(...dargs).then(vatDeliveryResults => - sendUplink(['deliverDone', vatDeliveryResults]), - ); - } else if (dtype === 'dropExports') { - doDropExports(...dargs).then(vatDeliveryResults => - sendUplink(['deliverDone', vatDeliveryResults]), - ); - } else { - assert.fail(X`bad delivery type ${dtype}`); - } + const [vatDeliveryObject] = margs; + deliver(vatDeliveryObject).then(vatDeliveryResults => + sendUplink(['deliverDone', vatDeliveryResults]), + ); } else { workerLog(`unrecognized downlink message ${type}`); } diff --git a/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js b/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js index fb85bcb0996..fb452cb8578 100644 --- a/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js +++ b/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js @@ -5,8 +5,10 @@ import { importBundle } from '@agoric/import-bundle'; import { makeMarshal } from '@agoric/marshal'; // grumble... waitUntilQuiescent is exported and closes over ambient authority import { waitUntilQuiescent } from '../../waitUntilQuiescent'; +import { insistVatDeliveryObject } from '../../message'; import { makeLiveSlots } from '../liveSlots'; +import { summarizeDelivery } from './deliver'; const encoder = new TextEncoder(); const decoder = new TextDecoder(); @@ -119,44 +121,22 @@ function runAndWait(f, errmsg) { * @param { ReturnType } port */ function makeWorker(port) { - /** @type { Record void> | null } */ + /** @type { ((delivery: VatDeliveryObject) => void) | null } */ let dispatch = null; - /** @type { (dr: Tagged, errmsg: string) => Promise } */ - async function doProcess(dispatchRecord, errmsg) { + /** @type { (delivery: VatDeliveryObject) => Promise } */ + async function deliver(delivery) { assert(dispatch); const theDispatch = dispatch; - const [dispatchOp, ...dispatchArgs] = dispatchRecord; - assert(typeof dispatchOp === 'string'); workerLog(`runAndWait`); - await runAndWait(() => theDispatch[dispatchOp](...dispatchArgs), errmsg); + const errmsg = summarizeDelivery(delivery); + await runAndWait(() => theDispatch(delivery), errmsg); workerLog(`doProcess done`); /** @type { Tagged } */ const vatDeliveryResults = harden(['ok']); return vatDeliveryResults; } - /** @type { (ts: unknown, msg: any) => Promise } */ - function doMessage(targetSlot, msg) { - const errmsg = `vat[${targetSlot}].${msg.method} dispatch failed`; - return doProcess( - ['deliver', targetSlot, msg.method, msg.args, msg.result], - errmsg, - ); - } - - /** @type { (rs: unknown) => Promise } */ - function doNotify(resolutions) { - const errmsg = `vat.notify failed`; - return doProcess(['notify', resolutions], errmsg); - } - - /** @type { (rs: unknown) => Promise } */ - function doDropExports(vrefs) { - const errmsg = `vat.dropExports failed`; - return doProcess(['dropExports', vrefs], errmsg); - } - /** * TODO: consider other methods per SES VirtualConsole. * See https://github.com/Agoric/agoric-sdk/issues/2146 @@ -277,18 +257,9 @@ function makeWorker(port) { } case 'deliver': { assert(dispatch, 'cannot deliver before setBundle'); - assert(Array.isArray(args[0])); - const [[dtype, ...dargs]] = args; - switch (dtype) { - case 'message': - return doMessage(dargs[0], dargs[1]); - case 'notify': - return doNotify(dargs[0]); - case 'dropExports': - return doDropExports(dargs[0]); - default: - assert.fail(X`bad delivery type ${dtype}`); - } + const [vatDeliveryObject] = args; + insistVatDeliveryObject(vatDeliveryObject); + return deliver(vatDeliveryObject); } default: workerLog('handleItem: bad tag', tag, args.length); diff --git a/packages/SwingSet/src/vats/comms/dispatch.js b/packages/SwingSet/src/vats/comms/dispatch.js index 1b4376154ca..38892fa381b 100644 --- a/packages/SwingSet/src/vats/comms/dispatch.js +++ b/packages/SwingSet/src/vats/comms/dispatch.js @@ -1,5 +1,6 @@ import { assert, details as X } from '@agoric/assert'; import { makeVatSlot, insistVatType, parseVatSlot } from '../../parseVatSlots'; +import { insistMessage } from '../../message'; import { getRemote } from './remote'; import { makeState, makeStateKit } from './state'; import { deliverToController } from './controller'; @@ -116,7 +117,31 @@ export function buildCommsDispatch( console.log(`-- comms ignoring dropExports`); } - const dispatch = harden({ deliver, notify, dropExports }); + function dispatch(vatDeliveryObject) { + const [type, ...args] = vatDeliveryObject; + switch (type) { + case 'message': { + const [targetSlot, msg] = args; + insistMessage(msg); + deliver(targetSlot, msg.method, msg.args, msg.result); + return; + } + case 'notify': { + const [resolutions] = args; + notify(resolutions); + return; + } + case 'dropExports': { + const [vrefs] = args; + dropExports(vrefs); + return; + } + default: + assert.fail(X`unknown delivery type ${type}`); + } + } + harden(dispatch); + debugState.set(dispatch, { state, clistKit }); return dispatch; diff --git a/packages/SwingSet/test/files-devices/bootstrap-0.js b/packages/SwingSet/test/files-devices/bootstrap-0.js index cabcea7f03f..e1da465802c 100644 --- a/packages/SwingSet/test/files-devices/bootstrap-0.js +++ b/packages/SwingSet/test/files-devices/bootstrap-0.js @@ -1,9 +1,10 @@ +import { extractMessage } from '../util'; + export default function setup(syscall, state, _helpers, vatPowers) { - const dispatch = harden({ - deliver(facetid, method, args, _result) { - vatPowers.testLog(args.body); - vatPowers.testLog(JSON.stringify(args.slots)); - }, - }); + function dispatch(vatDeliverObject) { + const { args } = extractMessage(vatDeliverObject); + vatPowers.testLog(args.body); + vatPowers.testLog(JSON.stringify(args.slots)); + } return dispatch; } diff --git a/packages/SwingSet/test/files-devices/bootstrap-1.js b/packages/SwingSet/test/files-devices/bootstrap-1.js index 5c569d65948..d11d8489ad3 100644 --- a/packages/SwingSet/test/files-devices/bootstrap-1.js +++ b/packages/SwingSet/test/files-devices/bootstrap-1.js @@ -1,22 +1,22 @@ import { assert, details as X } from '@agoric/assert'; +import { extractMessage } from '../util'; export default function setup(syscall, state, _helpers, vatPowers) { const { testLog } = vatPowers; let deviceRef; - const dispatch = harden({ - deliver(facetid, method, args, _result) { - if (method === 'bootstrap') { - const argb = JSON.parse(args.body); - const deviceIndex = argb[1].d1.index; - deviceRef = args.slots[deviceIndex]; - assert(deviceRef === 'd-70', X`bad deviceRef ${deviceRef}`); - } else if (method === 'step1') { - testLog(`callNow`); - const setArgs = harden({ body: JSON.stringify([1, 2]), slots: [] }); - const ret = syscall.callNow(deviceRef, 'set', setArgs); - testLog(JSON.stringify(ret)); - } - }, - }); + function dispatch(vatDeliverObject) { + const { method, args } = extractMessage(vatDeliverObject); + if (method === 'bootstrap') { + const argb = JSON.parse(args.body); + const deviceIndex = argb[1].d1.index; + deviceRef = args.slots[deviceIndex]; + assert(deviceRef === 'd-70', X`bad deviceRef ${deviceRef}`); + } else if (method === 'step1') { + testLog(`callNow`); + const setArgs = harden({ body: JSON.stringify([1, 2]), slots: [] }); + const ret = syscall.callNow(deviceRef, 'set', setArgs); + testLog(JSON.stringify(ret)); + } + } return dispatch; } diff --git a/packages/SwingSet/test/files-devices/bootstrap-4.js b/packages/SwingSet/test/files-devices/bootstrap-4.js index 24ad248c268..446743e430b 100644 --- a/packages/SwingSet/test/files-devices/bootstrap-4.js +++ b/packages/SwingSet/test/files-devices/bootstrap-4.js @@ -1,6 +1,7 @@ import { assert } from '@agoric/assert'; import { QCLASS } from '@agoric/marshal'; import { insistVatType } from '../../src/parseVatSlots'; +import { extractMessage } from '../util'; // to exercise the error we get when syscall.callNow() is given a promise // identifier, we must bypass liveslots, which would otherwise protect us @@ -17,32 +18,31 @@ function capargs(args, slots = []) { export default function setup(syscall, state, _helpers, vatPowers) { const { callNow } = syscall; const { testLog } = vatPowers; - const dispatch = harden({ - deliver(facetid, method, args, _result) { - if (method === 'bootstrap') { - // find the device slot - const [_vats, devices] = JSON.parse(args.body); - const qnode = devices.d0; - assert.equal(qnode[QCLASS], 'slot'); - const slot = args.slots[qnode.index]; - insistVatType('device', slot); + function dispatch(vatDeliverObject) { + const { method, args } = extractMessage(vatDeliverObject); + if (method === 'bootstrap') { + // find the device slot + const [_vats, devices] = JSON.parse(args.body); + const qnode = devices.d0; + assert.equal(qnode[QCLASS], 'slot'); + const slot = args.slots[qnode.index]; + insistVatType('device', slot); - const vpid = 'p+1'; // pretend we're exporting a promise - const pnode = { [QCLASS]: 'slot', index: 0 }; - const callNowArgs = capargs([pnode], [vpid]); + const vpid = 'p+1'; // pretend we're exporting a promise + const pnode = { [QCLASS]: 'slot', index: 0 }; + const callNowArgs = capargs([pnode], [vpid]); - testLog('sending Promise'); - try { - // this will throw an exception, but is also (eventually) vat-fatal - callNow(slot, 'send', callNowArgs); - testLog('oops: survived sending Promise'); - } catch (e) { - testLog('good: callNow failed'); - } - } else if (method === 'ping') { - testLog('oops: still alive'); + testLog('sending Promise'); + try { + // this will throw an exception, but is also (eventually) vat-fatal + callNow(slot, 'send', callNowArgs); + testLog('oops: survived sending Promise'); + } catch (e) { + testLog('good: callNow failed'); } - }, - }); + } else if (method === 'ping') { + testLog('oops: still alive'); + } + } return dispatch; } diff --git a/packages/SwingSet/test/liveslots-helpers.js b/packages/SwingSet/test/liveslots-helpers.js new file mode 100644 index 00000000000..99d32403d07 --- /dev/null +++ b/packages/SwingSet/test/liveslots-helpers.js @@ -0,0 +1,46 @@ +import { WeakRef, FinalizationRegistry } from '../src/weakref'; +import { makeLiveSlots } from '../src/kernel/liveSlots'; + +export function buildSyscall() { + const log = []; + + const syscall = { + send(targetSlot, method, args, resultSlot) { + log.push({ type: 'send', targetSlot, method, args, resultSlot }); + }, + subscribe(target) { + log.push({ type: 'subscribe', target }); + }, + resolve(resolutions) { + log.push({ type: 'resolve', resolutions }); + }, + dropImports(slots) { + log.push({ type: 'dropImports', slots }); + }, + exit(isFailure, info) { + log.push({ type: 'exit', isFailure, info }); + }, + }; + + return { log, syscall }; +} + +export function makeDispatch( + syscall, + build, + vatID = 'vatA', + enableDisavow = false, +) { + const gcTools = harden({ WeakRef, FinalizationRegistry }); + const { setBuildRootObject, dispatch } = makeLiveSlots( + syscall, + vatID, + {}, + {}, + undefined, + enableDisavow, + gcTools, + ); + setBuildRootObject(build); + return dispatch; +} diff --git a/packages/SwingSet/test/test-comms.js b/packages/SwingSet/test/test-comms.js index 194f5f23640..ecc450ccf5d 100644 --- a/packages/SwingSet/test/test-comms.js +++ b/packages/SwingSet/test/test-comms.js @@ -7,6 +7,7 @@ import { makeState, makeStateKit } from '../src/vats/comms/state'; import { makeCListKit } from '../src/vats/comms/clist'; import { addRemote } from '../src/vats/comms/remote'; import { debugState } from '../src/vats/comms/dispatch'; +import { makeMessage, makeDropExports } from './util'; const test = wrapTest(rawTest); @@ -49,8 +50,8 @@ test('transmit', t => { // look at machine A, on which some local vat is sending messages to a // remote 'bob' on machine B const { syscall, sends } = mockSyscall(); - const d = buildCommsDispatch(syscall, 'fakestate', 'fakehelpers'); - const { state, clistKit } = debugState.get(d); + const dispatch = buildCommsDispatch(syscall, 'fakestate', 'fakehelpers'); + const { state, clistKit } = debugState.get(dispatch); const { provideKernelForLocal, provideLocalForKernel, @@ -67,7 +68,7 @@ test('transmit', t => { // now tell the comms vat to send a message to a remote machine, the // equivalent of bob!foo() - d.deliver(bobKernel, 'foo', capdata('argsbytes', []), null); + dispatch(makeMessage(bobKernel, 'foo', capdata('argsbytes', []))); t.deepEqual(sends.shift(), [ transmitterID, 'transmit', @@ -75,11 +76,12 @@ test('transmit', t => { ]); // bob!bar(alice, bob) - d.deliver( - bobKernel, - 'bar', - capdata('argsbytes', [aliceKernel, bobKernel]), - null, + dispatch( + makeMessage( + bobKernel, + 'bar', + capdata('argsbytes', [aliceKernel, bobKernel]), + ), ); t.deepEqual(sends.shift(), [ transmitterID, @@ -89,11 +91,12 @@ test('transmit', t => { // the outbound ro-20 should match an inbound ro+20, both represent 'alice' t.is(getLocalForRemote(remoteID, 'ro+20'), aliceLocal); // do it again, should use same values - d.deliver( - bobKernel, - 'bar', - capdata('argsbytes', [aliceKernel, bobKernel]), - null, + dispatch( + makeMessage( + bobKernel, + 'bar', + capdata('argsbytes', [aliceKernel, bobKernel]), + ), ); t.deepEqual(sends.shift(), [ transmitterID, @@ -102,12 +105,13 @@ test('transmit', t => { ]); // bob!cat(alice, bob, ayana) - const ayana = 'o-11'; - d.deliver( - bobKernel, - 'cat', - capdata('argsbytes', [aliceKernel, bobKernel, ayana]), - null, + const ayanaKernel = 'o-11'; + dispatch( + makeMessage( + bobKernel, + 'cat', + capdata('argsbytes', [aliceKernel, bobKernel, ayanaKernel]), + ), ); t.deepEqual(sends.shift(), [ transmitterID, @@ -120,8 +124,8 @@ test('receive', t => { // look at machine B, which is receiving remote messages aimed at a local // vat's object 'bob' const { syscall, sends } = mockSyscall(); - const d = buildCommsDispatch(syscall, 'fakestate', 'fakehelpers'); - const { state, clistKit } = debugState.get(d); + const dispatch = buildCommsDispatch(syscall, 'fakestate', 'fakehelpers'); + const { state, clistKit } = debugState.get(dispatch); const { provideLocalForKernel, getKernelForLocal, @@ -140,20 +144,22 @@ test('receive', t => { // now pretend the transport layer received a message from remote1, as if // the remote machine had performed bob!foo() - d.deliver( - receiverID, - 'receive', - encodeArgs(`deliver:${bobRemote}:foo:;argsbytes`), - null, + dispatch( + makeMessage( + receiverID, + 'receive', + encodeArgs(`deliver:${bobRemote}:foo:;argsbytes`), + ), ); t.deepEqual(sends.shift(), [bobKernel, 'foo', capdata('argsbytes')]); // bob!bar(alice, bob) - d.deliver( - receiverID, - 'receive', - encodeArgs(`deliver:${bobRemote}:bar::ro-20:${bobRemote};argsbytes`), - null, + dispatch( + makeMessage( + receiverID, + 'receive', + encodeArgs(`deliver:${bobRemote}:bar::ro-20:${bobRemote};argsbytes`), + ), ); const expectedAliceKernel = 'o+31'; t.deepEqual(sends.shift(), [ @@ -168,11 +174,12 @@ test('receive', t => { t.is(getLocalForKernel(expectedAliceKernel), 'lo11'); // bob!bar(alice, bob), again, to test stability - d.deliver( - receiverID, - 'receive', - encodeArgs(`deliver:${bobRemote}:bar::ro-20:${bobRemote};argsbytes`), - null, + dispatch( + makeMessage( + receiverID, + 'receive', + encodeArgs(`deliver:${bobRemote}:bar::ro-20:${bobRemote};argsbytes`), + ), ); t.deepEqual(sends.shift(), [ bobKernel, @@ -182,11 +189,14 @@ test('receive', t => { // bob!cat(alice, bob, ayana) const expectedAyanaKernel = 'o+32'; - d.deliver( - receiverID, - 'receive', - encodeArgs(`deliver:${bobRemote}:cat::ro-20:${bobRemote}:ro-21;argsbytes`), - null, + dispatch( + makeMessage( + receiverID, + 'receive', + encodeArgs( + `deliver:${bobRemote}:cat::ro-20:${bobRemote}:ro-21;argsbytes`, + ), + ), ); t.deepEqual(sends.shift(), [ bobKernel, @@ -195,5 +205,5 @@ test('receive', t => { ]); // make sure comms can tolerate dropExports, even if it's a no-op - d.dropExports([expectedAliceKernel, expectedAyanaKernel]); + dispatch(makeDropExports(expectedAliceKernel, expectedAyanaKernel)); }); diff --git a/packages/SwingSet/test/test-kernel.js b/packages/SwingSet/test/test-kernel.js index ee558449a55..a99be987429 100644 --- a/packages/SwingSet/test/test-kernel.js +++ b/packages/SwingSet/test/test-kernel.js @@ -9,7 +9,7 @@ import { waitUntilQuiescent } from '../src/waitUntilQuiescent'; import buildKernel from '../src/kernel/index'; import { initializeKernel } from '../src/kernel/initializeKernel'; import { makeVatSlot } from '../src/parseVatSlots'; -import { checkKT } from './util'; +import { checkKT, extractMessage } from './util'; function capdata(body, slots = []) { return harden({ body, slots }); @@ -40,8 +40,8 @@ function checkPromises(t, kernel, expected) { } function emptySetup(_syscall) { - function deliver() {} - return { deliver }; + function dispatch() {} + return dispatch; } function makeConsole(tag) { @@ -83,11 +83,13 @@ test('simple call', async t => { await kernel.start(); const log = []; function setup1(syscall, state, _helpers, vatPowers) { - function deliver(facetID, method, args) { + function dispatch(vatDeliverObject) { + // TODO: just push the vatDeliverObject + const { facetID, method, args } = extractMessage(vatDeliverObject); log.push([facetID, method, args]); vatPowers.testLog(JSON.stringify({ facetID, method, args })); } - return { deliver }; + return dispatch; } await kernel.createTestVat('vat1', setup1); const vat1 = kernel.vatNameToID('vat1'); @@ -127,7 +129,8 @@ test('vat store', async t => { await kernel.start(); const log = []; function setup(syscall, _state, _helpers, _vatPowers) { - function deliver(facetID, method, args) { + function dispatch(vatDeliverObject) { + const { method, args } = extractMessage(vatDeliverObject); switch (method) { case 'get': { const v = syscall.vatstoreGet('zot'); @@ -148,7 +151,7 @@ test('vat store', async t => { assert.fail(X`this can't happen`); } } - return { deliver }; + return dispatch; } await kernel.createTestVat('vat', setup); const vat = kernel.vatNameToID('vat'); @@ -178,10 +181,11 @@ test('map inbound', async t => { await kernel.start(); const log = []; function setup1(_syscall) { - function deliver(facetID, method, args) { + function dispatch(vatDeliverObject) { + const { facetID, method, args } = extractMessage(vatDeliverObject); log.push([facetID, method, args]); } - return { deliver }; + return dispatch; } await kernel.createTestVat('vat1', setup1); await kernel.createTestVat('vat2', setup1); @@ -225,8 +229,8 @@ test('addImport', async t => { const kernel = makeKernel(); await kernel.start(); function setup(_syscall) { - function deliver(_facetID, _method, _args) {} - return { deliver }; + function dispatch() {} + return dispatch; } await kernel.createTestVat('vat1', setup); await kernel.createTestVat('vat2', setup); @@ -255,7 +259,8 @@ test('outbound call', async t => { nextPromiseIndex += 1; return makeVatSlot('promise', true, index); } - function deliver(facetID, method, args) { + function dispatch(vatDeliverObject) { + const { facetID, method, args } = extractMessage(vatDeliverObject); // console.log(`d1/${facetID} called`); log.push(['d1', facetID, method, args]); const pid = allocatePromise(); @@ -266,17 +271,18 @@ test('outbound call', async t => { pid, ); } - return { deliver }; + return dispatch; } await kernel.createTestVat('vat1', setup1); function setup2(_syscall) { - function deliver(facetID, method, args) { + function dispatch(vatDeliverObject) { + const { facetID, method, args } = extractMessage(vatDeliverObject); // console.log(`d2/${facetID} called`); log.push(['d2', facetID, method, args]); log.push(['d2 promises', kernel.dump().promises]); } - return { deliver }; + return dispatch; } await kernel.createTestVat('vat2', setup2); const vat1 = kernel.vatNameToID('vat1'); @@ -461,31 +467,34 @@ test('three-party', async t => { nextPromiseIndex += 1; return makeVatSlot('promise', true, index); } - function deliver(facetID, method, args) { + function dispatch(vatDeliverObject) { + const { facetID, method, args } = extractMessage(vatDeliverObject); console.log(`vatA/${facetID} called`); log.push(['vatA', facetID, method, args]); const pid = allocatePromise(); syscall.send(bobForA, 'intro', capdata('bargs', [carolForA]), pid); log.push(['vatA', 'promiseID', pid]); } - return { deliver }; + return dispatch; } await kernel.createTestVat('vatA', setupA); function setupB(_syscall) { - function deliver(facetID, method, args) { + function dispatch(vatDeliverObject) { + const { facetID, method, args } = extractMessage(vatDeliverObject); console.log(`vatB/${facetID} called`); log.push(['vatB', facetID, method, args]); } - return { deliver }; + return dispatch; } await kernel.createTestVat('vatB', setupB); function setupC(_syscall) { - function deliver(facetID, method, args) { + function dispatch(vatDeliverObject) { + const { facetID, method, args } = extractMessage(vatDeliverObject); log.push(['vatC', facetID, method, args]); } - return { deliver }; + return dispatch; } await kernel.createTestVat('vatC', setupC); @@ -586,10 +595,11 @@ test('transfer promise', async t => { const logA = []; function setupA(syscall) { syscallA = syscall; - function deliver(facetID, method, args) { + function dispatch(vatDeliverObject) { + const { facetID, method, args } = extractMessage(vatDeliverObject); logA.push([facetID, method, args]); } - return { deliver }; + return dispatch; } await kernel.createTestVat('vatA', setupA); @@ -597,10 +607,11 @@ test('transfer promise', async t => { const logB = []; function setupB(syscall) { syscallB = syscall; - function deliver(facetID, method, args) { + function dispatch(vatDeliverObject) { + const { facetID, method, args } = extractMessage(vatDeliverObject); logB.push([facetID, method, args]); } - return { deliver }; + return dispatch; } await kernel.createTestVat('vatB', setupB); @@ -689,10 +700,11 @@ test('subscribe to promise', async t => { const log = []; function setup(s) { syscall = s; - function deliver(facetID, method, args) { + function dispatch(vatDeliverObject) { + const { facetID, method, args } = extractMessage(vatDeliverObject); log.push(['deliver', facetID, method, args]); } - return { deliver }; + return dispatch; } await kernel.createTestVat('vat1', setup); await kernel.createTestVat('vat2', emptySetup); @@ -732,19 +744,18 @@ test('promise resolveToData', async t => { let syscallA; function setupA(s) { syscallA = s; - function deliver() {} - function notify(resolutions) { - log.push(['notify', resolutions]); + function dispatch(vatDeliverObject) { + log.push(vatDeliverObject); } - return { deliver, notify }; + return dispatch; } await kernel.createTestVat('vatA', setupA); let syscallB; function setupB(s) { syscallB = s; - function deliver() {} - return { deliver }; + function dispatch() {} + return dispatch; } await kernel.createTestVat('vatB', setupB); @@ -802,19 +813,18 @@ test('promise resolveToPresence', async t => { let syscallA; function setupA(s) { syscallA = s; - function deliver() {} - function notify(resolutions) { - log.push(['notify', resolutions]); + function dispatch(vatDeliverObject) { + log.push(vatDeliverObject); } - return { deliver, notify }; + return dispatch; } await kernel.createTestVat('vatA', setupA); let syscallB; function setupB(s) { syscallB = s; - function deliver() {} - return { deliver }; + function dispatch() {} + return dispatch; } await kernel.createTestVat('vatB', setupB); @@ -879,19 +889,18 @@ test('promise reject', async t => { let syscallA; function setupA(s) { syscallA = s; - function deliver() {} - function notify(resolutions) { - log.push(['notify', resolutions]); + function dispatch(vatDeliverObject) { + log.push(vatDeliverObject); } - return { deliver, notify }; + return dispatch; } await kernel.createTestVat('vatA', setupA); let syscallB; function setupB(s) { syscallB = s; - function deliver() {} - return { deliver }; + function dispatch() {} + return dispatch; } await kernel.createTestVat('vatB', setupB); @@ -947,12 +956,13 @@ test('transcript', async t => { await kernel.start(); function setup(syscall, _state) { - function deliver(facetID, _method, args) { + function dispatch(vatDeliverObject) { + const { facetID, args } = extractMessage(vatDeliverObject); if (facetID === aliceForAlice) { syscall.send(args.slots[1], 'foo', capdata('fooarg'), 'p+5'); } } - return { deliver }; + return dispatch; } await kernel.createTestVat('vatA', setup); await kernel.createTestVat('vatB', emptySetup); @@ -977,11 +987,13 @@ test('transcript', async t => { t.is(tr.length, 1); t.deepEqual(tr[0], { d: [ - 'deliver', + 'message', aliceForAlice, - 'store', - capdata('args string', [aliceForAlice, bobForAlice]), - 'p-60', + { + method: 'store', + args: capdata('args string', [aliceForAlice, bobForAlice]), + result: 'p-60', + }, ], syscalls: [ { @@ -1004,16 +1016,19 @@ test('non-pipelined promise queueing', async t => { let syscall; function setupA(s) { syscall = s; - function deliver() {} - return { deliver }; + function dispatch() {} + return dispatch; } await kernel.createTestVat('vatA', setupA); function setupB(_s) { - function deliver(target, method, args, result) { - log.push([target, method, args, result]); + function dispatch(vatDeliverObject) { + const { facetID, method, args, result } = extractMessage( + vatDeliverObject, + ); + log.push([facetID, method, args, result]); } - return { deliver }; + return dispatch; } await kernel.createTestVat('vatB', setupB); @@ -1126,16 +1141,19 @@ test('pipelined promise queueing', async t => { let syscall; function setupA(s) { syscall = s; - function deliver() {} - return { deliver }; + function dispatch() {} + return dispatch; } await kernel.createTestVat('vatA', setupA); function setupB(_s) { - function deliver(target, method, args, result) { - log.push([target, method, args, result]); + function dispatch(vatDeliverObject) { + const { facetID, method, args, result } = extractMessage( + vatDeliverObject, + ); + log.push([facetID, method, args, result]); } - return { deliver }; + return dispatch; } await kernel.createTestVat('vatB', setupB, {}, { enablePipelining: true }); diff --git a/packages/SwingSet/test/test-liveslots.js b/packages/SwingSet/test/test-liveslots.js index 17b0f5a3fcb..34313593fa6 100644 --- a/packages/SwingSet/test/test-liveslots.js +++ b/packages/SwingSet/test/test-liveslots.js @@ -3,73 +3,17 @@ import test from 'ava'; import { E } from '@agoric/eventual-send'; import { Far } from '@agoric/marshal'; import { assert, details as X } from '@agoric/assert'; -import { WeakRef, FinalizationRegistry } from '../src/weakref'; import { waitUntilQuiescent } from '../src/waitUntilQuiescent'; -import { makeLiveSlots } from '../src/kernel/liveSlots'; - -function capdata(body, slots = []) { - return harden({ body, slots }); -} - -function capargs(args, slots = []) { - return capdata(JSON.stringify(args), slots); -} - -function capdataOneSlot(slot) { - return capargs({ '@qclass': 'slot', iface: 'Alleged: export', index: 0 }, [ - slot, - ]); -} - -function capargsOneSlot(slot) { - return capargs( - [{ '@qclass': 'slot', iface: 'Alleged: export', index: 0 }], - [slot], - ); -} - -function oneResolution(promiseID, rejected, data) { - return [[promiseID, rejected, data]]; -} - -function buildSyscall() { - const log = []; - - const syscall = { - send(targetSlot, method, args, resultSlot) { - log.push({ type: 'send', targetSlot, method, args, resultSlot }); - }, - subscribe(target) { - log.push({ type: 'subscribe', target }); - }, - resolve(resolutions) { - log.push({ type: 'resolve', resolutions }); - }, - dropImports(slots) { - log.push({ type: 'dropImports', slots }); - }, - exit(isFailure, info) { - log.push({ type: 'exit', isFailure, info }); - }, - }; - - return { log, syscall }; -} - -function makeDispatch(syscall, build, enableDisavow = false) { - const gcTools = harden({ WeakRef, FinalizationRegistry }); - const { setBuildRootObject, dispatch } = makeLiveSlots( - syscall, - 'vatA', - {}, - {}, - undefined, - enableDisavow, - gcTools, - ); - setBuildRootObject(build); - return dispatch; -} +import { buildSyscall, makeDispatch } from './liveslots-helpers'; +import { + capargs, + capargsOneSlot, + capdataOneSlot, + makeMessage, + makeResolve, + makeReject, + makeDropExports, +} from './util'; test('calls', async t => { const { log, syscall } = buildSyscall(); @@ -93,24 +37,25 @@ test('calls', async t => { const rootA = 'o+0'; // root!one() // sendOnly - dispatch.deliver(rootA, 'one', capargs(['args']), undefined); + dispatch(makeMessage(rootA, 'one', capargs(['args']))); await waitUntilQuiescent(); t.deepEqual(log.shift(), 'one'); // pr = makePromise() // root!two(pr.promise) // pr.resolve('result') - dispatch.deliver( - rootA, - 'two', - capargs([{ '@qclass': 'slot', index: 0 }], ['p-1']), - undefined, + dispatch( + makeMessage( + rootA, + 'two', + capargs([{ '@qclass': 'slot', index: 0 }], ['p-1']), + ), ); await waitUntilQuiescent(); t.deepEqual(log.shift(), { type: 'subscribe', target: 'p-1' }); t.deepEqual(log.shift(), 'two true'); - dispatch.notify(oneResolution('p-1', false, capargs('result'))); + dispatch(makeResolve('p-1', capargs('result'))); await waitUntilQuiescent(); t.deepEqual(log.shift(), ['res', 'result']); @@ -118,17 +63,18 @@ test('calls', async t => { // root!two(pr.promise) // pr.reject('rejection') - dispatch.deliver( - rootA, - 'two', - capargs([{ '@qclass': 'slot', index: 0 }], ['p-2']), - undefined, + dispatch( + makeMessage( + rootA, + 'two', + capargs([{ '@qclass': 'slot', index: 0 }], ['p-2']), + ), ); await waitUntilQuiescent(); t.deepEqual(log.shift(), { type: 'subscribe', target: 'p-2' }); t.deepEqual(log.shift(), 'two true'); - dispatch.notify(oneResolution('p-2', true, capargs('rejection'))); + dispatch(makeReject('p-2', capargs('rejection'))); await waitUntilQuiescent(); t.deepEqual(log.shift(), ['rej', 'rejection']); @@ -157,11 +103,8 @@ test('liveslots pipelines to syscall.send', async t => { const p3 = 'p+7'; // root!one(x) // sendOnly - dispatch.deliver( - rootA, - 'one', - capargs([{ '@qclass': 'slot', index: 0 }], [x]), - undefined, + dispatch( + makeMessage(rootA, 'one', capargs([{ '@qclass': 'slot', index: 0 }], [x])), ); await waitUntilQuiescent(); @@ -222,7 +165,7 @@ test('liveslots pipeline/non-pipeline calls', async t => { const slot0arg = { '@qclass': 'slot', index: 0 }; // function deliver(target, method, argsdata, result) { - dispatch.deliver(rootA, 'one', capargs([slot0arg], [p1])); + dispatch(makeMessage(rootA, 'one', capargs([slot0arg], [p1]))); await waitUntilQuiescent(); // the vat should subscribe to the inbound p1 during deserialization t.deepEqual(log.shift(), { type: 'subscribe', target: p1 }); @@ -239,7 +182,7 @@ test('liveslots pipeline/non-pipeline calls', async t => { t.deepEqual(log, []); // now we tell it the promise has resolved, to object 'o2' - dispatch.notify(oneResolution(p1, false, capargs(slot0arg, [o2]))); + dispatch(makeResolve(p1, capargs(slot0arg, [o2]))); await waitUntilQuiescent(); // this allows E(o2).nonpipe2() to go out, which was not pipelined t.deepEqual(log.shift(), { @@ -255,7 +198,7 @@ test('liveslots pipeline/non-pipeline calls', async t => { // now call two(), which should send nonpipe3 to o2, not p1, since p1 has // been resolved - dispatch.deliver(rootA, 'two', capargs([], [])); + dispatch(makeMessage(rootA, 'two', capargs([], []))); await waitUntilQuiescent(); t.deepEqual(log.shift(), { type: 'send', @@ -332,7 +275,9 @@ async function doOutboundPromise(t, mode) { } // function deliver(target, method, argsdata, result) { - dispatch.deliver(rootA, 'run', capargs([slot0arg, resolution], [target])); + dispatch( + makeMessage(rootA, 'run', capargs([slot0arg, resolution], [target])), + ); await waitUntilQuiescent(); // The vat should send 'one' and mention the promise for the first time. It @@ -418,7 +363,7 @@ async function doResultPromise(t, mode) { const target2 = 'o-2'; // if it returns data or a rejection, two() results in an error - dispatch.deliver(rootA, 'run', capargs([slot0arg], [target1])); + dispatch(makeMessage(rootA, 'run', capargs([slot0arg], [target1]))); await waitUntilQuiescent(); // The vat should send 'getTarget2' and subscribe to the result promise @@ -447,13 +392,11 @@ async function doResultPromise(t, mode) { // resolve p1 first. The one() call was already pipelined, so this // should not trigger any new syscalls. if (mode === 'to presence') { - dispatch.notify( - oneResolution(expectedP1, false, capargs(slot0arg, [target2])), - ); + dispatch(makeResolve(expectedP1, capargs(slot0arg, [target2]))); } else if (mode === 'to data') { - dispatch.notify(oneResolution(expectedP1, false, capargs(4, []))); + dispatch(makeResolve(expectedP1, capargs(4, []))); } else if (mode === 'reject') { - dispatch.notify(oneResolution(expectedP1, true, capargs('error', []))); + dispatch(makeReject(expectedP1, capargs('error', []))); } else { assert.fail(X`unknown mode ${mode}`); } @@ -461,7 +404,7 @@ async function doResultPromise(t, mode) { t.deepEqual(log, []); // Now we resolve p2, allowing the second two() to proceed - dispatch.notify(oneResolution(expectedP2, false, capargs(4, []))); + dispatch(makeResolve(expectedP2, capargs(4, []))); await waitUntilQuiescent(); if (mode === 'to presence') { @@ -526,7 +469,7 @@ test('liveslots vs symbols', async t => { // E(root)[Symbol.asyncIterator]('one') const rp1 = 'p-1'; - dispatch.deliver(rootA, 'Symbol.asyncIterator', capargs(['one']), 'p-1'); + dispatch(makeMessage(rootA, 'Symbol.asyncIterator', capargs(['one']), 'p-1')); await waitUntilQuiescent(); t.deepEqual(log.shift(), { type: 'resolve', @@ -535,11 +478,12 @@ test('liveslots vs symbols', async t => { t.deepEqual(log, []); // root~.good(target) -> send(methodname=Symbol.asyncIterator) - dispatch.deliver( - rootA, - 'good', - capargs([{ '@qclass': 'slot', index: 0 }], [target]), - undefined, + dispatch( + makeMessage( + rootA, + 'good', + capargs([{ '@qclass': 'slot', index: 0 }], [target]), + ), ); await waitUntilQuiescent(); t.deepEqual(log.shift(), { @@ -560,11 +504,13 @@ test('liveslots vs symbols', async t => { message: 'arbitrary Symbols cannot be used as method names', name: 'Error', }; - dispatch.deliver( - rootA, - 'bad', - capargs([{ '@qclass': 'slot', index: 0 }], [target]), - rp2, + dispatch( + makeMessage( + rootA, + 'bad', + capargs([{ '@qclass': 'slot', index: 0 }], [target]), + rp2, + ), ); await waitUntilQuiescent(); t.deepEqual(log.shift(), { @@ -584,12 +530,12 @@ test('disable disavow', async t => { }, }); } - const dispatch = makeDispatch(syscall, build, false); + const dispatch = makeDispatch(syscall, build, 'vatA', false); t.deepEqual(log, []); const rootA = 'o+0'; // root~.one() // sendOnly - dispatch.deliver(rootA, 'one', capargs([]), undefined); + dispatch(makeMessage(rootA, 'one', capargs([]))); await waitUntilQuiescent(); t.deepEqual(log.shift(), false); t.deepEqual(log, []); @@ -642,13 +588,13 @@ test('disavow', async t => { }); return root; } - const dispatch = makeDispatch(syscall, build, true); + const dispatch = makeDispatch(syscall, build, 'vatA', true); t.deepEqual(log, []); const rootA = 'o+0'; const import1 = 'o-1'; // root~.one(import1) // sendOnly - dispatch.deliver(rootA, 'one', capargsOneSlot(import1), undefined); + dispatch(makeMessage(rootA, 'one', capargsOneSlot(import1))); await waitUntilQuiescent(); t.deepEqual(log.shift(), { type: 'dropImports', slots: [import1] }); t.deepEqual(log.shift(), 'disavowed pres1'); @@ -694,14 +640,14 @@ test('dropExports', async t => { }); return root; } - const dispatch = makeDispatch(syscall, build, true); + const dispatch = makeDispatch(syscall, build, 'vatA', true); t.deepEqual(log, []); const rootA = 'o+0'; // rp1 = root~.one() // ex1 = await rp1 const rp1 = 'p-1'; - dispatch.deliver(rootA, 'one', capargs([]), rp1); + dispatch(makeMessage(rootA, 'one', capargs([]), rp1)); await waitUntilQuiescent(); const l1 = log.shift(); const ex1 = l1.resolutions[0][2].slots[0]; @@ -712,6 +658,6 @@ test('dropExports', async t => { t.deepEqual(log, []); // now tell the vat to drop that export - dispatch.dropExports([ex1]); + dispatch(makeDropExports(ex1)); // for now, all that we care about is that liveslots doesn't crash }); diff --git a/packages/SwingSet/test/test-vpid-liveslots.js b/packages/SwingSet/test/test-vpid-liveslots.js index 813abae4d7f..e381a8873c5 100644 --- a/packages/SwingSet/test/test-vpid-liveslots.js +++ b/packages/SwingSet/test/test-vpid-liveslots.js @@ -1,6 +1,3 @@ -// eslint-disable-next-line no-redeclare -/* global setImmediate */ - import '@agoric/install-ses'; import test from 'ava'; @@ -8,42 +5,9 @@ import { E } from '@agoric/eventual-send'; import { makePromiseKit } from '@agoric/promise-kit'; import { assert, details as X } from '@agoric/assert'; import { Far } from '@agoric/marshal'; -import { WeakRef, FinalizationRegistry } from '../src/weakref'; -import { makeLiveSlots } from '../src/kernel/liveSlots'; - -function capdata(body, slots = []) { - return harden({ body, slots }); -} - -function capargs(args, slots = []) { - return capdata(JSON.stringify(args), slots); -} - -function oneResolution(promiseID, rejected, data) { - return [[promiseID, rejected, data]]; -} - -function buildSyscall() { - const log = []; - - const syscall = { - send(targetSlot, method, args, resultSlot) { - log.push({ type: 'send', targetSlot, method, args, resultSlot }); - }, - subscribe(target) { - log.push({ type: 'subscribe', target }); - }, - resolve(resolutions) { - log.push({ type: 'resolve', resolutions }); - }, - }; - - return { log, syscall }; -} - -function endOfCrank() { - return new Promise(resolve => setImmediate(() => resolve())); -} +import { waitUntilQuiescent } from '../src/waitUntilQuiescent'; +import { buildSyscall, makeDispatch } from './liveslots-helpers'; +import { makeMessage, makeResolve, makeReject, capargs } from './util'; function hush(p) { p.then( @@ -188,22 +152,6 @@ function resolutionOf(vpid, mode, targets) { return resolution; } -function makeDispatch(syscall, build, vatID = 'vatA') { - function vatDecref() {} - const gcTools = harden({ WeakRef, FinalizationRegistry, vatDecref }); - const { setBuildRootObject, dispatch } = makeLiveSlots( - syscall, - vatID, - {}, - {}, - undefined, - false, - gcTools, - ); - setBuildRootObject(build); - return dispatch; -} - async function doVatResolveCase1(t, mode) { // case 1 const { log, syscall } = buildSyscall(); @@ -234,12 +182,14 @@ async function doVatResolveCase1(t, mode) { const expectedP3 = 'p+7'; const expectedP4 = 'p+8'; - dispatch.deliver( - rootA, - 'run', - capargs([slot0arg, slot1arg], [target1, target2]), + dispatch( + makeMessage( + rootA, + 'run', + capargs([slot0arg, slot1arg], [target1, target2]), + ), ); - await endOfCrank(); + await waitUntilQuiescent(); // The vat should send 'one' and subscribe to the result promise t.deepEqual(log.shift(), { @@ -390,24 +340,26 @@ async function doVatResolveCase23(t, which, mode, stalls) { const target2 = 'o-2'; if (which === 2) { - dispatch.deliver(rootA, 'result', capargs([], []), p1); - dispatch.deliver(rootA, 'promise', capargs([slot0arg], [p1])); + dispatch(makeMessage(rootA, 'result', capargs([], []), p1)); + dispatch(makeMessage(rootA, 'promise', capargs([slot0arg], [p1]))); } else if (which === 3) { - dispatch.deliver(rootA, 'promise', capargs([slot0arg], [p1])); - dispatch.deliver(rootA, 'result', capargs([], []), p1); + dispatch(makeMessage(rootA, 'promise', capargs([slot0arg], [p1]))); + dispatch(makeMessage(rootA, 'result', capargs([], []), p1)); } else { assert.fail(X`bad which=${which}`); } - await endOfCrank(); + await waitUntilQuiescent(); t.deepEqual(log.shift(), { type: 'subscribe', target: p1 }); t.deepEqual(log, []); - dispatch.deliver( - rootA, - 'run', - capargs([slot0arg, slot1arg], [target1, target2]), + dispatch( + makeMessage( + rootA, + 'run', + capargs([slot0arg, slot1arg], [target1, target2]), + ), ); - await endOfCrank(); + await waitUntilQuiescent(); // At the end of the turn in which run() is executed, the promise queue // will contain deliveries one() and two() (specifically invocations of the @@ -464,8 +416,8 @@ async function doVatResolveCase23(t, which, mode, stalls) { // `syscall.resolve(vpid1, stuff)` into the kernel, notifying any remote // subscribers that p1 has been resolved. Since the vat is also a subscriber, // thenResolve's callback must also invoke p1's resolver (which was stashed in - // importedPromisesByPromiseID), as if the kernel had call the vat's - // dispatch.notify. This causes the p1.then callback to be pushed to the back + // importedPromisesByPromiseID), as if the kernel had called the vat's + // dispatch(notify). This causes the p1.then callback to be pushed to the back // of the promise queue, which will set resolutionOfP1 after all the syscalls // have been made. @@ -607,13 +559,13 @@ async function doVatResolveCase4(t, mode) { } const target2 = 'o-2'; - dispatch.deliver(rootA, 'get', capargs([slot0arg], [p1])); - await endOfCrank(); + dispatch(makeMessage(rootA, 'get', capargs([slot0arg], [p1]))); + await waitUntilQuiescent(); t.deepEqual(log.shift(), { type: 'subscribe', target: p1 }); t.deepEqual(log, []); - dispatch.deliver(rootA, 'first', capargs([slot0arg], [target1])); - await endOfCrank(); + dispatch(makeMessage(rootA, 'first', capargs([slot0arg], [target1]))); + await waitUntilQuiescent(); const expectedP2 = nextP(); t.deepEqual(log.shift(), { @@ -636,26 +588,28 @@ async function doVatResolveCase4(t, mode) { t.deepEqual(log.shift(), { type: 'subscribe', target: expectedP3 }); t.deepEqual(log, []); + let r; if (mode === 'presence') { - dispatch.notify(oneResolution(p1, false, capargs(slot0arg, [target2]))); + r = makeResolve(p1, capargs(slot0arg, [target2])); } else if (mode === 'local-object') { - dispatch.notify(oneResolution(p1, false, capargs(slot0arg, [rootA]))); + r = makeResolve(p1, capargs(slot0arg, [rootA])); } else if (mode === 'data') { - dispatch.notify(oneResolution(p1, false, capargs(4, []))); + r = makeResolve(p1, capargs(4, [])); } else if (mode === 'promise-data') { - dispatch.notify(oneResolution(p1, false, capargs([slot0arg], [p1]))); + r = makeResolve(p1, capargs([slot0arg], [p1])); } else if (mode === 'reject') { - dispatch.notify(oneResolution(p1, true, capargs('error', []))); + r = makeReject(p1, capargs('error', [])); } else if (mode === 'promise-reject') { - dispatch.notify(oneResolution(p1, true, capargs([slot0arg], [p1]))); + r = makeReject(p1, capargs([slot0arg], [p1])); } else { assert.fail(X`unknown mode ${mode}`); } - await endOfCrank(); + dispatch(r); + await waitUntilQuiescent(); t.deepEqual(log, []); - dispatch.deliver(rootA, 'second', capargs([slot0arg], [target1])); - await endOfCrank(); + dispatch(makeMessage(rootA, 'second', capargs([slot0arg], [target1]))); + await waitUntilQuiescent(); const expectedP4 = nextP(); const expectedP5 = nextP(); @@ -734,16 +688,16 @@ test('inter-vat circular promise references', async t => { // const pbB = 'p-18'; // const paB = 'p-19'; - dispatchA.deliver(rootA, 'genPromise', capargs([], []), paA); - await endOfCrank(); + dispatchA(makeMessage(rootA, 'genPromise', capargs([], []), paA)); + await waitUntilQuiescent(); t.deepEqual(log, []); - // dispatchB.deliver(rootB, 'genPromise', capargs([], []), pbB); - // await endOfCrank(); + // dispatchB(makeMessage(rootB, 'genPromise', capargs([], []), pbB)); + // await waitUntilQuiescent(); // t.deepEqual(log, []); - dispatchA.deliver(rootA, 'usePromise', capargs([[slot0arg]], [pbA])); - await endOfCrank(); + dispatchA(makeMessage(rootA, 'usePromise', capargs([[slot0arg]], [pbA]))); + await waitUntilQuiescent(); t.deepEqual(log.shift(), { type: 'subscribe', target: pbA }); t.deepEqual(log.shift(), { type: 'resolve', @@ -751,8 +705,8 @@ test('inter-vat circular promise references', async t => { }); t.deepEqual(log, []); - // dispatchB.deliver(rootB, 'usePromise', capargs([[slot0arg]], [paB])); - // await endOfCrank(); + // dispatchB(makeMessage(rootB, 'usePromise', capargs([[slot0arg]], [paB]))); + // await waitUntilQuiescent(); // t.deepEqual(log.shift(), { type: 'subscribe', target: paB }); // t.deepEqual(log.shift(), { // type: 'resolve', diff --git a/packages/SwingSet/test/util.js b/packages/SwingSet/test/util.js index 32844981d15..7060716e9cd 100644 --- a/packages/SwingSet/test/util.js +++ b/packages/SwingSet/test/util.js @@ -1,3 +1,5 @@ +import { assert } from '@agoric/assert'; + function compareArraysOfStrings(a, b) { a = a.join(' '); b = b.join(' '); @@ -36,22 +38,33 @@ export function dumpKT(kernel) { export function buildDispatch(onDispatchCallback = undefined) { const log = []; - const dispatch = { - deliver(targetSlot, method, args, resultSlot) { - const d = { type: 'deliver', targetSlot, method, args, resultSlot }; + function dispatch(vatDeliverObject) { + const [type, ...vdoargs] = vatDeliverObject; + if (type === 'message') { + const [target, msg] = vdoargs; + const { method, args, result } = msg; + const d = { + type: 'deliver', + targetSlot: target, + method, + args, + resultSlot: result, + }; log.push(d); if (onDispatchCallback) { onDispatchCallback(d); } - }, - notify(resolutions) { + } else if (type === 'notify') { + const [resolutions] = vdoargs; const d = { type: 'notify', resolutions }; log.push(d); if (onDispatchCallback) { onDispatchCallback(d); } - }, - }; + } else { + throw Error(`unknown vatDeliverObject type ${type}`); + } + } return { log, dispatch }; } @@ -62,3 +75,55 @@ export function ignore(p) { () => 0, ); } + +export function extractMessage(vatDeliverObject) { + const [type, ...vdoargs] = vatDeliverObject; + assert.equal(type, 'message'); + const [facetID, msg] = vdoargs; + const { method, args, result } = msg; + return { facetID, method, args, result }; +} + +function capdata(body, slots = []) { + return harden({ body, slots }); +} + +export function capargs(args, slots = []) { + return capdata(JSON.stringify(args), slots); +} + +export function capdataOneSlot(slot) { + return capargs({ '@qclass': 'slot', iface: 'Alleged: export', index: 0 }, [ + slot, + ]); +} + +export function capargsOneSlot(slot) { + return capargs( + [{ '@qclass': 'slot', iface: 'Alleged: export', index: 0 }], + [slot], + ); +} + +export function makeMessage(target, method, args, result = undefined) { + const msg = { method, args, result }; + const vatDeliverObject = harden(['message', target, msg]); + return vatDeliverObject; +} + +export function makeResolve(target, result) { + const resolutions = [[target, false, result]]; + const vatDeliverObject = harden(['notify', resolutions]); + return vatDeliverObject; +} + +export function makeReject(target, result) { + const resolutions = [[target, true, result]]; + const vatDeliverObject = harden(['notify', resolutions]); + return vatDeliverObject; +} + +export function makeDropExports(...vrefs) { + const vatDeliverObject = harden(['dropExports', vrefs]); + return vatDeliverObject; +} diff --git a/packages/SwingSet/test/vat-controller-1 b/packages/SwingSet/test/vat-controller-1 index 9bfd9417a9c..a192d623cd1 100644 --- a/packages/SwingSet/test/vat-controller-1 +++ b/packages/SwingSet/test/vat-controller-1 @@ -1,7 +1,9 @@ // -*- js -*- +import { extractMessage } from './util'; export default function setup(syscall, _state, _helpers, vatPowers) { - function deliver(target, method, args) { - vatPowers.testLog(JSON.stringify({ target, method, args })); + function dispatch(vatDeliverObject) { + const { facetID, method, args } = extractMessage(vatDeliverObject); + vatPowers.testLog(JSON.stringify({ target: facetID, method, args })); } - return { deliver }; + return dispatch; } diff --git a/packages/SwingSet/test/vat-syscall-failure.js b/packages/SwingSet/test/vat-syscall-failure.js index 533be174bec..a761e80040e 100644 --- a/packages/SwingSet/test/vat-syscall-failure.js +++ b/packages/SwingSet/test/vat-syscall-failure.js @@ -1,3 +1,5 @@ +import { extractMessage } from './util'; + function capdata(body, slots = []) { return harden({ body, slots }); } @@ -7,10 +9,11 @@ function capargs(args, slots = []) { } export default function setup(syscall, _state, _helpers, vatPowers) { - function deliver(target, method, args) { + function dispatch(vatDeliverObject) { + const { method, args } = extractMessage(vatDeliverObject); vatPowers.testLog(`${method}`); const thing = method === 'begood' ? args.slots[0] : 'o-3414159'; syscall.send(thing, 'pretendToBeAThing', capargs([method])); } - return { deliver }; + return dispatch; }