@agoric/swingset-vat
Advanced tools
Comparing version 0.0.3 to 0.0.4
{ | ||
"name": "@agoric/swingset-vat", | ||
"version": "0.0.3", | ||
"version": "0.0.4", | ||
"description": "Vat/Container Launcher", | ||
@@ -18,3 +18,3 @@ "main": "src/main.js", | ||
"publish": "npm run-script pack && npm publish --access public", | ||
"test": "npm run-script build-kernel && tape -r esm test/test-node-version.js && tape -r esm test/**/test*.js", | ||
"test": "npm run-script build-kernel && tape -r esm test/test-node-version.js && tape -r esm 'test/**/test*.js' | tap-spec", | ||
"pretty-fix": "prettier --write '**/*.{js,jsx}'", | ||
@@ -36,2 +36,3 @@ "pretty-check": "prettier --check '**/*.{js,jsx}'", | ||
"rollup-plugin-node-resolve": "^4.0.0", | ||
"tap-spec": "^5.0.0", | ||
"tape": "^4.10.0", | ||
@@ -38,0 +39,0 @@ "tape-promise": "^4.0.0" |
@@ -21,12 +21,9 @@ /* global setImmediate */ | ||
} | ||
if (dirent.name.startsWith('vat-')) { | ||
let name; | ||
let indexJS; | ||
if (dirent.isFile() && dirent.name.endsWith('.js')) { | ||
name = dirent.name.slice('vat-'.length, -'.js'.length); | ||
indexJS = path.resolve(basedir, dirent.name); | ||
} else if (dirent.isDirectory()) { | ||
name = dirent.name.slice('vat-'.length); | ||
indexJS = path.resolve(basedir, dirent.name, 'index.js'); | ||
} | ||
if ( | ||
dirent.name.startsWith('vat-') && | ||
dirent.isFile() && | ||
dirent.name.endsWith('.js') | ||
) { | ||
const name = dirent.name.slice('vat-'.length, -'.js'.length); | ||
const indexJS = path.resolve(basedir, dirent.name); | ||
vatSources.set(name, indexJS); | ||
@@ -49,3 +46,3 @@ } | ||
} | ||
return harden({ vatSources, bootstrapIndexJS, state }); | ||
return { vatSources, bootstrapIndexJS, state }; | ||
} | ||
@@ -97,3 +94,3 @@ | ||
async function addVat(vatID, sourceIndex, options) { | ||
async function addVat(vatID, sourceIndex, _options) { | ||
if (!(sourceIndex[0] === '.' || path.isAbsolute(sourceIndex))) { | ||
@@ -110,6 +107,2 @@ throw Error( | ||
let setup; | ||
// note: the contents of this 'devices' object are kernel-realm, but | ||
// the object itself is host-realm. kernel.addVat is responsible for | ||
// unpacking it safely and not exposing the container to vat code. | ||
const devices = {}; | ||
@@ -128,24 +121,28 @@ if (withSES) { | ||
setup = s.evaluate(source, { require: r })(); | ||
if (options.devices) { | ||
Object.getOwnPropertyNames(options.devices).forEach(name => { | ||
const d = options.devices[name]; | ||
devices[name] = s.evaluate(`(${d.attenuatorSource})`, { require: r })( | ||
d, | ||
); | ||
}); | ||
} | ||
} else { | ||
// eslint-disable-next-line global-require,import/no-dynamic-require | ||
setup = require(`${sourceIndex}`).default; | ||
if (options.devices) { | ||
Object.getOwnPropertyNames(options.devices).forEach(name => { | ||
const d = options.devices[name]; | ||
// eslint-disable-next-line no-eval | ||
devices[name] = eval(d.attenuatorSource)(d); | ||
}); | ||
} | ||
} | ||
kernel.addVat(vatID, setup, devices); | ||
kernel.addVat(vatID, setup); | ||
} | ||
async function addDevice(name, sourceIndex, endowments) { | ||
if (!(sourceIndex[0] === '.' || path.isAbsolute(sourceIndex))) { | ||
throw Error( | ||
'sourceIndex must be relative (./foo) or absolute (/foo) not bare (foo)', | ||
); | ||
} | ||
let setup; | ||
if (withSES) { | ||
let source = await bundleSource(`${sourceIndex}`); | ||
source = `(${source})`; | ||
setup = s.evaluate(source, { require: r })(); | ||
} else { | ||
// eslint-disable-next-line global-require,import/no-dynamic-require | ||
setup = require(`${sourceIndex}`).default; | ||
} | ||
kernel.addDevice(name, setup, endowments); | ||
} | ||
// the kernel won't leak our objects into the Vats, we must do | ||
@@ -188,2 +185,9 @@ // the same in this wrapper | ||
if (config.devices) { | ||
for (const [name, srcpath, endowments] of config.devices) { | ||
// eslint-disable-next-line no-await-in-loop | ||
await addDevice(name, srcpath, endowments); | ||
} | ||
} | ||
if (config.vatSources) { | ||
@@ -190,0 +194,0 @@ for (const vatID of config.vatSources.keys()) { |
import harden from '@agoric/harden'; | ||
import Nat from '@agoric/nat'; | ||
import { makeLiveSlots } from './liveSlots'; | ||
import { makeDeviceSlots } from './deviceSlots'; | ||
import { makeCommsSlots } from './commsSlots/index'; | ||
import { QCLASS, makeMarshal } from './marshal'; | ||
import makePromise from './makePromise'; | ||
import makeVatManager from './vatManager'; | ||
import makeDeviceManager from './deviceManager'; | ||
@@ -21,27 +24,8 @@ export default function buildKernel(kernelEndowments) { | ||
// 'id' is always a non-negative integer (a Nat) | ||
// 'slot' is a type(string)+id, plus maybe a vatid (string) | ||
// * 'relative slot' lacks a vatid because it is implicit | ||
// used in syscall.send and dispatch.deliver | ||
// * 'absolute slot' includes an explicit vatid | ||
// used in kernel's runQueue | ||
// 'slots' is an array of 'slot', in send/deliver | ||
// 'slotIndex' is an index into 'slots', used in serialized JSON | ||
// key = `${type}.${toVatID}.${slotID}` | ||
// vats[vatid] = { imports: { inbound[key] = importID, | ||
// outbound[importID] = slot, }, | ||
// nextImportID, | ||
// promises: { inbound[kernelPromiseID] = id, | ||
// outbound[id] = kernelPromiseID, }, | ||
// nextPromiseID, | ||
// resolvers: { inbound[kernelPromiseID] = id, | ||
// outbound[id] = kernelPromiseID, }, | ||
// nextResolverID, | ||
// } | ||
const vats = harden(new Map()); | ||
const devices = harden(new Map()); | ||
// runQueue entries are {type, vatID, more..}. 'more' depends on type: | ||
// * deliver: target, msg | ||
// * notifyFulfillToData/notifyFulfillToTarget/notifyReject: kernelPromiseID | ||
// * notifyFulfillToData/notifyFulfillToPresence/notifyReject: kernelPromiseID | ||
const runQueue = []; | ||
@@ -54,10 +38,2 @@ | ||
function getVat(vatID) { | ||
const vat = vats.get(vatID); | ||
if (vat === undefined) { | ||
throw new Error(`unknown vatID '${vatID}'`); | ||
} | ||
return vat; | ||
} | ||
function allocateKernelPromiseIndex() { | ||
@@ -69,135 +45,2 @@ const i = nextPromiseIndex; | ||
function allocatePromiseIDForVat(vat) { | ||
const i = vat.nextPromiseID; | ||
vat.nextPromiseID += 1; | ||
return i; | ||
} | ||
function allocateResolverIDForVat(vat) { | ||
const i = vat.nextResolverID; | ||
vat.nextResolverID += 1; | ||
return i; | ||
} | ||
// we define three types of slot identifiers: inbound, neutral, outbound | ||
// * outbound is what syscall.send(slots=) contains, it is always scoped to | ||
// the sending vat, and the values are {type, id}. 'type' is either | ||
// 'import' ("my import") or 'export'. Message targets are always imports. | ||
// * middle is stored in runQueue, and contains {type: export, vatID, slotID} | ||
// * inbound is passed into deliver(slots=), is always scoped to the | ||
// receiving/target vat, and the values are {type, id} where 'type' is | ||
// either 'export' (for arguments coming back home) or 'import' for | ||
// arguments from other vats | ||
// | ||
// * To convert outbound->middle, we look up imports in kernelSlots, and | ||
// just append the sending vatID to exports | ||
// | ||
// * To convert middle->inbound, we set attach type='export' when the vatID | ||
// matches that of the receiving vat, and we look up the others in | ||
// kernelSlots (adding one if necessary) to deliver type='import' | ||
// mapOutbound: convert fromVatID-relative slot to absolute slot. fromVatID | ||
// just did syscall.send and referenced 'slot' (in an argument, or as the | ||
// target of a send), what are they talking about? | ||
function mapOutbound(fromVatID, slot) { | ||
// kdebug(`mapOutbound ${JSON.stringify(slot)}`); | ||
const vat = getVat(fromVatID); | ||
if (slot.type === 'export') { | ||
// one of fromVatID's exports, so just make the vatID explicit | ||
Nat(slot.id); | ||
return { type: 'export', vatID: fromVatID, id: slot.id }; | ||
} | ||
if (slot.type === 'import') { | ||
// an import from somewhere else, so look in the sending Vat's table to | ||
// translate into absolute form | ||
Nat(slot.id); | ||
return vat.imports.outbound.get(slot.id); | ||
} | ||
if (slot.type === 'promise') { | ||
Nat(slot.id); | ||
if (!vat.promises.outbound.has(slot.id)) { | ||
throw new Error(`unknown promise slot '${slot.id}'`); | ||
} | ||
return { type: 'promise', id: vat.promises.outbound.get(slot.id) }; | ||
} | ||
if (slot.type === 'resolver') { | ||
Nat(slot.id); | ||
if (!vat.resolvers.outbound.has(slot.id)) { | ||
throw new Error(`unknown resolver slot '${slot.id}'`); | ||
} | ||
return { type: 'resolver', id: vat.resolvers.outbound.get(slot.id) }; | ||
} | ||
throw Error(`unknown slot.type '${slot.type}'`); | ||
} | ||
function allocateImportIndex(vatID) { | ||
const vat = getVat(vatID); | ||
const i = vat.nextImportID; | ||
vat.nextImportID = i + 1; | ||
return i; | ||
} | ||
// mapInbound: convert from absolute slot to forVatID-relative slot. This | ||
// is used when building the arguments for dispatch.deliver. | ||
function mapInbound(forVatID, slot) { | ||
// kdebug(`mapInbound for ${forVatID} of ${slot}`); | ||
const vat = getVat(forVatID); | ||
if (slot.type === 'export') { | ||
const { vatID, id } = slot; | ||
Nat(id); | ||
if (vatID === forVatID) { | ||
// this is returning home, so it's one of the target's exports | ||
return { type: 'export', id }; | ||
} | ||
const m = vat.imports; | ||
const key = `${slot.type}.${vatID}.${id}`; // ugh javascript | ||
if (!m.inbound.has(key)) { | ||
// must add both directions | ||
const newSlotID = Nat(allocateImportIndex(forVatID)); | ||
// kdebug(` adding ${newSlotID}`); | ||
m.inbound.set(key, newSlotID); | ||
m.outbound.set(newSlotID, harden({ type: 'export', vatID, id })); | ||
} | ||
return { type: 'import', id: m.inbound.get(key) }; | ||
} | ||
if (slot.type === 'promise') { | ||
const kernelPromiseID = slot.id; | ||
Nat(kernelPromiseID); | ||
const m = vat.promises; | ||
if (!m.inbound.has(kernelPromiseID)) { | ||
const promiseID = Nat(allocatePromiseIDForVat(vat)); | ||
m.inbound.set(kernelPromiseID, promiseID); | ||
m.outbound.set(promiseID, kernelPromiseID); | ||
} | ||
return { type: 'promise', id: m.inbound.get(kernelPromiseID) }; | ||
} | ||
if (slot.type === 'resolver') { | ||
const kernelPromiseID = slot.id; | ||
Nat(kernelPromiseID); | ||
const m = vat.resolvers; | ||
if (!m.inbound.has(kernelPromiseID)) { | ||
const resolverID = Nat(allocateResolverIDForVat(vat)); | ||
kdebug( | ||
` mapInbound allocating resID ${resolverID} for kpid ${kernelPromiseID}`, | ||
); | ||
m.inbound.set(kernelPromiseID, resolverID); | ||
m.outbound.set(resolverID, kernelPromiseID); | ||
} | ||
return { type: 'resolver', id: m.inbound.get(kernelPromiseID) }; | ||
} | ||
throw Error(`unknown type '${slot.type}'`); | ||
} | ||
/* | ||
@@ -250,3 +93,3 @@ function chaseRedirections(promiseID) { | ||
reject(msg.kernelPromiseID, makeError(s), []); | ||
} else if (kp.state === 'fulfilledToTarget') { | ||
} else if (kp.state === 'fulfilledToPresence') { | ||
send(kp.fulfillSlot, msg); | ||
@@ -290,3 +133,3 @@ } else if (kp.state === 'rejected') { | ||
function fulfillToTarget(id, targetSlot) { | ||
function fulfillToPresence(id, targetSlot) { | ||
if (!kernelPromises.has(id)) { | ||
@@ -301,9 +144,9 @@ throw new Error(`unknown kernelPromise id '${id}'`); | ||
throw new Error( | ||
`fulfillToTarget() must fulfill to export, not ${targetSlot.type}`, | ||
`fulfillToPresence() must fulfill to export, not ${targetSlot.type}`, | ||
); | ||
} | ||
p.state = 'fulfilledToTarget'; | ||
p.state = 'fulfilledToPresence'; | ||
p.fulfillSlot = targetSlot; | ||
notifySubscribersAndQueue(id, p, 'notifyFulfillToTarget'); | ||
notifySubscribersAndQueue(id, p, 'notifyFulfillToPresence'); | ||
delete p.subscribers; | ||
@@ -350,2 +193,10 @@ delete p.decider; | ||
function invoke(device, method, data, slots) { | ||
const dev = devices.get(device.deviceName); | ||
if (!dev) { | ||
throw new Error(`unknown deviceRef ${JSON.stringify(device)}`); | ||
} | ||
return dev.manager.invoke(device, method, data, slots); | ||
} | ||
async function process(f, then, logerr) { | ||
@@ -372,4 +223,2 @@ // the delivery might cause some number of (native) Promises to be | ||
kdebug, | ||
mapOutbound, | ||
mapInbound, | ||
createPromiseWithDecider, | ||
@@ -380,9 +229,10 @@ send, | ||
fulfillToData, | ||
fulfillToTarget, | ||
fulfillToPresence, | ||
reject, | ||
log, | ||
process, | ||
invoke, | ||
}; | ||
function addVat(vatID, setup, devices) { | ||
function addVat(vatID, setup) { | ||
if (vats.has(vatID)) { | ||
@@ -394,2 +244,3 @@ throw new Error(`already have a vat named '${vatID}'`); | ||
makeLiveSlots, | ||
makeCommsSlots, | ||
log(str) { | ||
@@ -400,29 +251,34 @@ log.push(`${str}`); | ||
// the vatManager invokes setup() to build the userspace image | ||
const manager = makeVatManager( | ||
const manager = makeVatManager(vatID, syscallManager, setup, helpers); | ||
vats.set( | ||
vatID, | ||
harden({ | ||
id: vatID, | ||
manager, | ||
}), | ||
); | ||
} | ||
function addDevice(name, setup, endowments) { | ||
if (devices.has(name)) { | ||
throw new Error(`already have a device named '${name}'`); | ||
} | ||
const helpers = harden({ | ||
name, | ||
makeDeviceSlots, | ||
log(str) { | ||
log.push(`${str}`); | ||
}, | ||
}); | ||
const manager = makeDeviceManager( | ||
name, | ||
syscallManager, | ||
setup, | ||
helpers, | ||
devices, | ||
endowments, | ||
); | ||
// the vat record is not hardened: it holds mutable next-ID values | ||
vats.set(vatID, { | ||
id: vatID, | ||
devices.set(name, { | ||
id: name, | ||
manager, | ||
imports: harden({ | ||
outbound: new Map(), | ||
inbound: new Map(), | ||
}), | ||
// make these IDs start at different values to detect errors better | ||
nextImportID: 10, | ||
promises: harden({ | ||
outbound: new Map(), | ||
inbound: new Map(), | ||
}), | ||
nextPromiseID: 20, | ||
resolvers: harden({ | ||
outbound: new Map(), | ||
inbound: new Map(), | ||
}), | ||
nextResolverID: 30, | ||
}); | ||
@@ -432,3 +288,3 @@ } | ||
function addImport(forVatID, what) { | ||
return mapInbound(forVatID, what); | ||
return vats.get(forVatID).manager.mapInbound(what); | ||
} | ||
@@ -444,2 +300,9 @@ | ||
} | ||
if (s.type === 'device') { | ||
return harden({ | ||
type: `${s.type}`, | ||
deviceName: `${s.deviceName}`, | ||
id: Nat(s.id), | ||
}); | ||
} | ||
throw Error(`unrecognized type '${s.type}'`); | ||
@@ -498,22 +361,49 @@ } | ||
// bootstrap object to call itself, though. | ||
const vref = harden({}); // marker | ||
const vref = harden({ | ||
toString() { | ||
return targetVatID; | ||
}, | ||
}); // marker | ||
vatObj0s[targetVatID] = vref; | ||
vrefs.set(vref, { type: 'export', vatID: targetVatID, id: 0 }); | ||
console.log(`adding vref ${targetVatID}`); | ||
}); | ||
function serializeSlot(vref, slots, slotMap) { | ||
if (!vrefs.has(vref)) { | ||
console.log(`oops ${vref}`, vref); | ||
throw Error('bootstrap got unexpected pass-by-presence'); | ||
} | ||
if (!slotMap.has(vref)) { | ||
const drefs = new Map(); | ||
// we cannot serialize empty objects as pass-by-copy, because we decided | ||
// to make them pass-by-presence for use as EQ-able markers (eg for | ||
// Purses). So if we don't have any devices defined, we must add a dummy | ||
// entry to this object so it will serialize as pass-by-copy. We can | ||
// remove the dummy entry after we add the 'addVat' device | ||
const deviceObj0s = { _dummy: 'dummy' }; | ||
Array.from(devices.entries()).forEach(d => { | ||
const name = d[0]; | ||
const dref = harden({}); | ||
deviceObj0s[name] = dref; | ||
drefs.set(dref, { type: 'device', deviceName: name, id: 0 }); | ||
console.log(`adding dref ${name}`); | ||
}); | ||
if (Object.getOwnPropertyNames(deviceObj0s) === 0) { | ||
throw new Error('pass-by-copy rules require at least one device'); | ||
} | ||
function serializeSlot(ref, slots, slotMap) { | ||
if (!slotMap.has(ref)) { | ||
const slotIndex = slots.length; | ||
slots.push(vrefs.get(vref)); | ||
slotMap.set(vref, slotIndex); | ||
if (vrefs.has(ref)) { | ||
slots.push(vrefs.get(ref)); | ||
slotMap.set(ref, slotIndex); | ||
} else if (drefs.has(ref)) { | ||
slots.push(drefs.get(ref)); | ||
slotMap.set(ref, slotIndex); | ||
} else { | ||
console.log(`oops ${ref}`, ref); | ||
throw Error('bootstrap got unexpected pass-by-presence'); | ||
} | ||
} | ||
const slotIndex = slotMap.get(vref); | ||
const slotIndex = slotMap.get(ref); | ||
return harden({ [QCLASS]: 'slot', index: slotIndex }); | ||
} | ||
const m = makeMarshal(serializeSlot); | ||
const s = m.serialize(harden({ args: [argv, vatObj0s] })); | ||
const s = m.serialize(harden({ args: [argv, vatObj0s, deviceObj0s] })); | ||
// queueToExport() takes 'neutral' { type: export, vatID, slotID } objects in s.slots | ||
@@ -532,21 +422,3 @@ queueToExport(vatID, 0, 'bootstrap', s.argsString, s.slots); | ||
vatTables.push(vatTable); | ||
vat.imports.outbound.forEach((target, slot) => { | ||
kernelTable.push([ | ||
vatID, | ||
'import', | ||
slot, | ||
target.type, | ||
target.vatID, | ||
target.id, | ||
]); | ||
}); | ||
vat.promises.outbound.forEach((kernelPromiseID, id) => { | ||
kernelTable.push([vatID, 'promise', id, kernelPromiseID]); | ||
}); | ||
vat.resolvers.outbound.forEach((kernelPromiseID, id) => { | ||
kernelTable.push([vatID, 'resolver', id, kernelPromiseID]); | ||
}); | ||
vat.manager.dumpTables().forEach(e => kernelTable.push(e)); | ||
}); | ||
@@ -593,3 +465,3 @@ | ||
const kernel = harden({ | ||
addVat(vatID, setup, devices = {}) { | ||
addVat(vatID, setup) { | ||
harden(setup); | ||
@@ -602,13 +474,14 @@ // 'setup' must be an in-realm function. This test guards against | ||
} | ||
const kernelRealmDevices = {}; | ||
Object.getOwnPropertyNames(devices).forEach(name => { | ||
const d = devices[name]; | ||
if (!(d instanceof Object)) { | ||
throw Error(`device[${name}] is not an in-realm object`); | ||
} | ||
kernelRealmDevices[name] = d; | ||
}); | ||
addVat(`${vatID}`, setup, kernelRealmDevices); | ||
addVat(`${vatID}`, setup); | ||
}, | ||
addDevice(deviceName, setup, endowments) { | ||
console.log(`kernel.addDevice(${deviceName})`); | ||
harden(setup); | ||
if (!(setup instanceof Function)) { | ||
throw Error('setup is not an in-realm function'); | ||
} | ||
addDevice(`${deviceName}`, setup, endowments); | ||
}, | ||
callBootstrap, | ||
@@ -664,23 +537,9 @@ | ||
vats.forEach((vat, vatID) => { | ||
vatTables[vatID] = { | ||
nextImportID: vat.nextImportID, | ||
imports: { | ||
outbound: Array.from(vat.imports.outbound.entries()), | ||
inbound: Array.from(vat.imports.inbound.entries()), | ||
}, | ||
vatTables[vatID] = { state: vat.manager.getCurrentState() }; | ||
Object.assign(vatTables[vatID], vat.manager.getManagerState()); | ||
}); | ||
nextPromiseID: vat.nextPromiseID, | ||
promises: { | ||
outbound: Array.from(vat.promises.outbound.entries()), | ||
inbound: Array.from(vat.promises.inbound.entries()), | ||
}, | ||
nextResolverID: vat.nextResolverID, | ||
resolvers: { | ||
outbound: Array.from(vat.resolvers.outbound.entries()), | ||
inbound: Array.from(vat.resolvers.inbound.entries()), | ||
}, | ||
state: vat.manager.getCurrentState(), | ||
}; | ||
const deviceState = {}; | ||
devices.forEach((d, deviceName) => { | ||
deviceState[deviceName] = d.manager.getCurrentState(); | ||
}); | ||
@@ -700,2 +559,3 @@ | ||
vats: vatTables, | ||
devices: deviceState, | ||
runQueue, | ||
@@ -722,37 +582,2 @@ promises, | ||
const vat = vats.get(vatID); | ||
if ( | ||
vat.imports.outbound.size || | ||
vat.imports.inbound.size || | ||
vat.promises.inbound.size || | ||
vat.promises.inbound.size || | ||
vat.resolvers.inbound.size || | ||
vat.resolvers.inbound.size | ||
) { | ||
throw new Error(`vat[$vatID] is not empty, cannot loadState`); | ||
} | ||
vat.nextImportID = vatData.nextImportID; | ||
vatData.imports.outbound.forEach(kv => | ||
vat.imports.outbound.set(kv[0], kv[1]), | ||
); | ||
vatData.imports.inbound.forEach(kv => | ||
vat.imports.inbound.set(kv[0], kv[1]), | ||
); | ||
vat.nextPromiseID = vatData.nextPromiseID; | ||
vatData.promises.outbound.forEach(kv => | ||
vat.promises.outbound.set(kv[0], kv[1]), | ||
); | ||
vatData.promises.inbound.forEach(kv => | ||
vat.promises.inbound.set(kv[0], kv[1]), | ||
); | ||
vat.nextResolverID = vatData.nextResolverID; | ||
vatData.resolvers.outbound.forEach(kv => | ||
vat.resolvers.outbound.set(kv[0], kv[1]), | ||
); | ||
vatData.resolvers.inbound.forEach(kv => | ||
vat.resolvers.inbound.set(kv[0], kv[1]), | ||
); | ||
// this shouldn't be doing any syscalls, which is good because we | ||
@@ -762,4 +587,11 @@ // haven't wired anything else up yet | ||
await vat.manager.loadState(vatData.state); | ||
vat.manager.loadManagerState(vatData); | ||
} | ||
for (const deviceName of Object.getOwnPropertyNames(state.devices)) { | ||
const deviceData = state.devices[deviceName]; | ||
const device = devices.get(deviceName); | ||
device.manager.loadState(deviceData); | ||
} | ||
state.runQueue.forEach(q => runQueue.push(q)); | ||
@@ -766,0 +598,0 @@ |
@@ -11,5 +11,2 @@ import harden from '@agoric/harden'; | ||
// The E() wrapper does not yet return a Promise for the result of the method | ||
// call. | ||
function build(syscall, _state, makeRoot, forVatID) { | ||
@@ -29,2 +26,8 @@ const enableLSDebug = false; | ||
function makeDeviceNode(id) { | ||
return harden({ | ||
[`_deviceID_${id}`]() {}, | ||
}); | ||
} | ||
const outstandingProxies = new WeakSet(); | ||
@@ -36,3 +39,4 @@ | ||
slot.type === 'import' || | ||
slot.type === 'promise' | ||
slot.type === 'promise' || | ||
slot.type === 'deviceImport' | ||
) { | ||
@@ -144,2 +148,4 @@ return `${slot.type}-${Nat(slot.id)}`; | ||
val = importPromise(slot.id); | ||
} else if (slot.type === 'deviceImport') { | ||
val = makeDeviceNode(slot.id); | ||
} else { | ||
@@ -284,2 +290,4 @@ throw Error(`unrecognized slot.type '${slot.type}'`); | ||
handler = PresenceHandler(slot); | ||
} else if (slot && slot.type === 'deviceImport') { | ||
throw new Error(`E() does not accept device nodes`); | ||
} else { | ||
@@ -298,2 +306,33 @@ // might be a local object (previously sent or not), or a local Promise | ||
function DeviceHandler(slot) { | ||
return { | ||
get(target, prop) { | ||
if (prop !== `${prop}`) { | ||
return undefined; | ||
} | ||
return (...args) => { | ||
const ser = m.serialize(harden({ args })); | ||
const ret = syscall.callNow(slot, prop, ser.argsString, ser.slots); | ||
const retval = m.unserialize(ret.data, ret.slots); | ||
return retval; | ||
}; | ||
}, | ||
}; | ||
} | ||
function D(x) { | ||
// results = D(devicenode).name(args) | ||
if (outstandingProxies.has(x)) { | ||
throw new Error('D(D(x)) is invalid'); | ||
} | ||
const slot = valToSlot.get(x); | ||
if (!slot || slot.type !== 'deviceImport') { | ||
throw new Error('D() must be given a device node'); | ||
} | ||
const handler = DeviceHandler(slot); | ||
const pr = harden(new Proxy({}, handler)); | ||
outstandingProxies.add(pr); | ||
return pr; | ||
} | ||
function deliver(facetid, method, argsbytes, caps, resolverID) { | ||
@@ -347,3 +386,3 @@ lsdebug( | ||
if (slot.type === 'import' || slot.type === 'export') { | ||
syscall.fulfillToTarget(resolverID, slot); | ||
syscall.fulfillToPresence(resolverID, slot); | ||
} | ||
@@ -386,4 +425,4 @@ } else { | ||
function notifyFulfillToTarget(promiseID, slot) { | ||
lsdebug(`ls.dispatch.notifyFulfillToTarget(${promiseID}, ${slot})`); | ||
function notifyFulfillToPresence(promiseID, slot) { | ||
lsdebug(`ls.dispatch.notifyFulfillToPresence(${promiseID}, ${slot})`); | ||
if (!importedPromisesByPromiseID.has(promiseID)) { | ||
@@ -405,3 +444,3 @@ throw new Error(`unknown promiseID '${promiseID}'`); | ||
const rootObject = makeRoot(E); | ||
const rootObject = makeRoot(E, D); | ||
mustPassByPresence(rootObject); | ||
@@ -417,3 +456,3 @@ const rootSlot = { type: 'export', id: 0 }; | ||
notifyFulfillToData, | ||
notifyFulfillToTarget, | ||
notifyFulfillToPresence, | ||
notifyReject, | ||
@@ -427,3 +466,3 @@ }; | ||
notifyFulfillToData, | ||
notifyFulfillToTarget, | ||
notifyFulfillToPresence, | ||
notifyReject, | ||
@@ -434,3 +473,3 @@ } = build(syscall, state, makeRoot, forVatID); | ||
notifyFulfillToData, | ||
notifyFulfillToTarget, | ||
notifyFulfillToPresence, | ||
notifyReject, | ||
@@ -437,0 +476,0 @@ }); |
import harden from '@agoric/harden'; | ||
import Nat from '@agoric/nat'; | ||
export default function makeVatManager( | ||
vatID, | ||
syscallManager, | ||
setup, | ||
helpers, | ||
devices, | ||
) { | ||
export default function makeVatManager(vatID, syscallManager, setup, helpers) { | ||
const { | ||
kdebug, | ||
mapOutbound, | ||
mapInbound, | ||
createPromiseWithDecider, | ||
@@ -20,8 +12,251 @@ send, | ||
fulfillToData, | ||
fulfillToTarget, | ||
fulfillToPresence, | ||
reject, | ||
log, | ||
process, | ||
invoke, | ||
} = syscallManager; | ||
// We use vat-centric terminology here, so "inbound" means "into a vat", | ||
// generally from the kernel. We also have "comms vats" which use special | ||
// device access to interact with remote machines: messages from those | ||
// remote machines are "inbound" into the comms vats. Conversely "outbound" | ||
// means "out of a vat", usually into the local kernel, but we also use | ||
// "outbound" to describe the messages a comms vat is sending over a socket | ||
// or other communications channel. | ||
// The mapInbound() function is used to translate slot references on the | ||
// vat->kernel pathway. mapOutbound() is used for kernel->vat. | ||
// The terms "import" and "export" are also vat-centric. "import" means | ||
// something a Vat has imported (from the kernel). Imports are tracked in a | ||
// kernel-side table for each Vat, which is populated by the kernel as a | ||
// message is delivered. Each import is represented inside the Vat as a | ||
// Presence (at least when using liveSlots). | ||
// "exports" are callable objects inside the Vat which it has made | ||
// available to the kernel (so that other vats can invoke it). The exports | ||
// table is managed by userspace code inside the vat. The kernel tables map | ||
// one vat's import IDs to a pair of (exporting vat, vat's export-id) in | ||
// `vats[vatid].imports.outbound[importID]`. To make sure we use the same | ||
// importID each time, we also need to keep a reverse table: | ||
// `vats[vatid].imports.inbound` maps the (exporting-vat, export-id) back | ||
// to the importID. | ||
// Comms vats will have their own internal tables to track references | ||
// shared with other machines. These will have mapInbound/mapOutbound too. | ||
// A message arriving on a communication channel will pass through the | ||
// comms vat's mapInbound to figure out which "machine export" is the | ||
// target, which maps to a "vat import" (coming from the kernel). The | ||
// arguments might also be machine exports (for arguments that are "coming | ||
// home"), or more commonly will be new machine imports (for arguments that | ||
// point to other machines, usually the sending machine). The machine | ||
// imports will be presented to the kernel as exports of the comms vat. | ||
// 'id' is always a non-negative integer (a Nat) | ||
// 'slot' is a type(string)+id, plus maybe a vatid (string) | ||
// * 'relative slot' lacks a vatid because it is implicit | ||
// used in syscall.send and dispatch.deliver | ||
// * 'absolute slot' includes an explicit vatid | ||
// used in kernel's runQueue | ||
// 'slots' is an array of 'slot', in send/deliver | ||
// 'slotIndex' is an index into 'slots', used in serialized JSON | ||
// key = `${type}.${toVatID}.${slotID}` | ||
// tables = { imports: { inbound[key] = importID, | ||
// outbound[importID] = slot, }, | ||
// nextImportID, | ||
// promises: { inbound[kernelPromiseID] = id, | ||
// outbound[id] = kernelPromiseID, }, | ||
// nextPromiseID, | ||
// resolvers: { inbound[kernelPromiseID] = id, | ||
// outbound[id] = kernelPromiseID, }, | ||
// nextResolverID, | ||
// } | ||
// per-vat translation tables | ||
const tables = { | ||
imports: harden({ | ||
outbound: new Map(), | ||
inbound: new Map(), | ||
}), | ||
// make these IDs start at different values to detect errors better | ||
nextImportID: 10, | ||
promises: harden({ | ||
outbound: new Map(), | ||
inbound: new Map(), | ||
}), | ||
nextPromiseID: 20, | ||
resolvers: harden({ | ||
outbound: new Map(), | ||
inbound: new Map(), | ||
}), | ||
nextResolverID: 30, | ||
nextDeviceImportID: 40, | ||
deviceImports: harden({ | ||
outbound: new Map(), | ||
inbound: new Map(), | ||
}), | ||
}; | ||
function allocatePromiseID() { | ||
const i = tables.nextPromiseID; | ||
tables.nextPromiseID += 1; | ||
return i; | ||
} | ||
function allocateResolverID() { | ||
const i = tables.nextResolverID; | ||
tables.nextResolverID += 1; | ||
return i; | ||
} | ||
function allocateDeviceImportID() { | ||
const i = tables.nextDeviceImportID; | ||
tables.nextDeviceImportID += 1; | ||
return i; | ||
} | ||
// we define three types of slot identifiers: inbound, neutral, outbound | ||
// * outbound is what syscall.send(slots=) contains, it is always scoped to | ||
// the sending vat, and the values are {type, id}. 'type' is either | ||
// 'import' ("my import") or 'export'. Message targets are always imports. | ||
// * middle is stored in runQueue, and contains {type: export, vatID, slotID} | ||
// * inbound is passed into deliver(slots=), is always scoped to the | ||
// receiving/target vat, and the values are {type, id} where 'type' is | ||
// either 'export' (for arguments coming back home) or 'import' for | ||
// arguments from other vats | ||
// | ||
// * To convert outbound->middle, we look up imports in kernelSlots, and | ||
// just append the sending vatID to exports | ||
// | ||
// * To convert middle->inbound, we set attach type='export' when the vatID | ||
// matches that of the receiving vat, and we look up the others in | ||
// kernelSlots (adding one if necessary) to deliver type='import' | ||
// mapOutbound: convert fromVatID-relative slot to absolute slot. fromVatID | ||
// just did syscall.send and referenced 'slot' (in an argument, or as the | ||
// target of a send), what are they talking about? | ||
function mapOutbound(slot) { | ||
// kdebug(`mapOutbound ${JSON.stringify(slot)}`); | ||
if (slot.type === 'export') { | ||
// one of our exports, so just make the vatID explicit | ||
Nat(slot.id); | ||
return { type: 'export', vatID, id: slot.id }; | ||
} | ||
if (slot.type === 'import') { | ||
// an import from somewhere else, so look in the sending Vat's table to | ||
// translate into absolute form | ||
Nat(slot.id); | ||
return tables.imports.outbound.get(slot.id); | ||
} | ||
if (slot.type === 'deviceImport') { | ||
Nat(slot.id); | ||
return tables.deviceImports.outbound.get(slot.id); | ||
} | ||
if (slot.type === 'promise') { | ||
Nat(slot.id); | ||
if (!tables.promises.outbound.has(slot.id)) { | ||
throw new Error(`unknown promise slot '${slot.id}'`); | ||
} | ||
return { type: 'promise', id: tables.promises.outbound.get(slot.id) }; | ||
} | ||
if (slot.type === 'resolver') { | ||
Nat(slot.id); | ||
if (!tables.resolvers.outbound.has(slot.id)) { | ||
throw new Error(`unknown resolver slot '${slot.id}'`); | ||
} | ||
return { type: 'resolver', id: tables.resolvers.outbound.get(slot.id) }; | ||
} | ||
throw Error(`unknown slot.type in '${JSON.stringify(slot)}'`); | ||
} | ||
function allocateImportIndex() { | ||
const i = tables.nextImportID; | ||
tables.nextImportID = i + 1; | ||
return i; | ||
} | ||
// mapInbound: convert from absolute slot to forVatID-relative slot. This | ||
// is used when building the arguments for dispatch.deliver. | ||
function mapInbound(slot) { | ||
kdebug(`mapInbound for ${vatID} of ${JSON.stringify(slot)}`); | ||
if (slot.type === 'export') { | ||
const { vatID: fromVatID, id } = slot; | ||
Nat(id); | ||
if (vatID === fromVatID) { | ||
// this is returning home, so it's one of the target's exports | ||
return { type: 'export', id }; | ||
} | ||
const m = tables.imports; | ||
const key = `${slot.type}.${fromVatID}.${id}`; // ugh javascript | ||
if (!m.inbound.has(key)) { | ||
// must add both directions | ||
const newSlotID = Nat(allocateImportIndex()); | ||
// kdebug(` adding ${newSlotID}`); | ||
m.inbound.set(key, newSlotID); | ||
m.outbound.set( | ||
newSlotID, | ||
harden({ type: 'export', vatID: fromVatID, id }), // TODO just 'slot'? | ||
); | ||
} | ||
return { type: 'import', id: m.inbound.get(key) }; | ||
} | ||
if (slot.type === 'device') { | ||
const { deviceName, id } = slot; | ||
Nat(id); | ||
const m = tables.deviceImports; | ||
const key = `${slot.type}.${deviceName}.${id}`; // ugh javascript | ||
if (!m.inbound.has(key)) { | ||
// must add both directions | ||
const newSlotID = Nat(allocateDeviceImportID()); | ||
// kdebug(` adding ${newSlotID}`); | ||
m.inbound.set(key, newSlotID); | ||
m.outbound.set(newSlotID, harden(slot)); | ||
} | ||
return { type: 'deviceImport', id: m.inbound.get(key) }; | ||
} | ||
if (slot.type === 'promise') { | ||
const kernelPromiseID = slot.id; | ||
Nat(kernelPromiseID); | ||
const m = tables.promises; | ||
if (!m.inbound.has(kernelPromiseID)) { | ||
const promiseID = Nat(allocatePromiseID()); | ||
m.inbound.set(kernelPromiseID, promiseID); | ||
m.outbound.set(promiseID, kernelPromiseID); | ||
} | ||
return { type: 'promise', id: m.inbound.get(kernelPromiseID) }; | ||
} | ||
if (slot.type === 'resolver') { | ||
const kernelPromiseID = slot.id; | ||
Nat(kernelPromiseID); | ||
const m = tables.resolvers; | ||
if (!m.inbound.has(kernelPromiseID)) { | ||
const resolverID = Nat(allocateResolverID()); | ||
kdebug( | ||
` mapInbound allocating resID ${resolverID} for kpid ${kernelPromiseID}`, | ||
); | ||
m.inbound.set(kernelPromiseID, resolverID); | ||
m.outbound.set(resolverID, kernelPromiseID); | ||
} | ||
return { type: 'resolver', id: m.inbound.get(kernelPromiseID) }; | ||
} | ||
throw Error(`unknown type '${slot.type}'`); | ||
} | ||
let inReplay = false; | ||
@@ -53,3 +288,3 @@ const transcript = []; | ||
} | ||
const target = mapOutbound(vatID, targetSlot); | ||
const target = mapOutbound(targetSlot); | ||
if (!target) { | ||
@@ -67,3 +302,3 @@ throw Error( | ||
); | ||
const slots = vatSlots.map(slot => mapOutbound(vatID, slot)); | ||
const slots = vatSlots.map(slot => mapOutbound(slot)); | ||
// who will decide the answer? If the message is being queued for a | ||
@@ -86,3 +321,3 @@ // promise, then the kernel will decide (when the answer gets | ||
send(target, msg); | ||
const p = mapInbound(vatID, { | ||
const p = mapInbound({ | ||
type: 'promise', | ||
@@ -96,7 +331,7 @@ id: kernelPromiseID, | ||
const kernelPromiseID = createPromiseWithDecider(vatID); | ||
const p = mapInbound(vatID, { | ||
const p = mapInbound({ | ||
type: 'promise', | ||
id: kernelPromiseID, | ||
}); | ||
const r = mapInbound(vatID, { | ||
const r = mapInbound({ | ||
type: 'resolver', | ||
@@ -114,3 +349,3 @@ id: kernelPromiseID, | ||
function doSubscribe(promiseID) { | ||
const { id } = mapOutbound(vatID, { | ||
const { id } = mapOutbound({ | ||
type: 'promise', | ||
@@ -138,5 +373,5 @@ id: promiseID, | ||
// otherwise it's already resolved, you probably want to know how | ||
} else if (p.state === 'fulfilledToTarget') { | ||
} else if (p.state === 'fulfilledToPresence') { | ||
runQueue.push({ | ||
type: 'notifyFulfillToTarget', | ||
type: 'notifyFulfillToPresence', | ||
vatID, | ||
@@ -164,3 +399,3 @@ kernelPromiseID: id, | ||
function doRedirect(resolverID, targetPromiseID) { | ||
const { id } = mapOutbound(vatID, { type: 'resolver', id: resolverID }); | ||
const { id } = mapOutbound({ type: 'resolver', id: resolverID }); | ||
if (!kernelPromises.has(id)) { | ||
@@ -174,3 +409,3 @@ throw new Error(`unknown kernelPromise id '${id}'`); | ||
let { id: targetID } = mapOutbound(vatID, { type: 'promise', id: targetPromiseID }); | ||
let { id: targetID } = mapOutbound({ type: 'promise', id: targetPromiseID }); | ||
if (!kernelPromises.has(targetID)) { | ||
@@ -199,3 +434,3 @@ throw new Error(`unknown kernelPromise id '${targetID}'`); | ||
Nat(resolverID); | ||
const { id } = mapOutbound(vatID, { | ||
const { id } = mapOutbound({ | ||
type: 'resolver', | ||
@@ -207,3 +442,3 @@ id: resolverID, | ||
} | ||
const slots = vatSlots.map(slot => mapOutbound(vatID, slot)); | ||
const slots = vatSlots.map(slot => mapOutbound(slot)); | ||
kdebug( | ||
@@ -217,5 +452,5 @@ `syscall[${vatID}].fulfillData(vatid=${resolverID}/kid=${id}) = ${fulfillData} v=${JSON.stringify( | ||
function doFulfillToTarget(resolverID, slot) { | ||
function doFulfillToPresence(resolverID, slot) { | ||
Nat(resolverID); | ||
const { id } = mapOutbound(vatID, { | ||
const { id } = mapOutbound({ | ||
type: 'resolver', | ||
@@ -228,9 +463,9 @@ id: resolverID, | ||
const targetSlot = mapOutbound(vatID, slot); | ||
const targetSlot = mapOutbound(slot); | ||
kdebug( | ||
`syscall[${vatID}].fulfillToTarget(vatid=${resolverID}/kid=${id}) = vat:${JSON.stringify( | ||
`syscall[${vatID}].fulfillToPresence(vatid=${resolverID}/kid=${id}) = vat:${JSON.stringify( | ||
targetSlot, | ||
)}=ker:${id})`, | ||
); | ||
fulfillToTarget(id, targetSlot); | ||
fulfillToPresence(id, targetSlot); | ||
} | ||
@@ -240,3 +475,3 @@ | ||
Nat(resolverID); | ||
const { id } = mapOutbound(vatID, { | ||
const { id } = mapOutbound({ | ||
type: 'resolver', | ||
@@ -248,3 +483,3 @@ id: resolverID, | ||
} | ||
const slots = vatSlots.map(slot => mapOutbound(vatID, slot)); | ||
const slots = vatSlots.map(slot => mapOutbound(slot)); | ||
kdebug( | ||
@@ -258,2 +493,20 @@ `syscall[${vatID}].reject(vatid=${resolverID}/kid=${id}) = ${rejectData} v=${JSON.stringify( | ||
function doCallNow(target, method, argsString, argsSlots) { | ||
const dev = mapOutbound(target); | ||
if (dev.type !== 'device') { | ||
throw new Error( | ||
`doCallNow must target a device, not ${JSON.stringify(target)}`, | ||
); | ||
} | ||
const slots = argsSlots.map(slot => mapOutbound(slot)); | ||
kdebug( | ||
`syscall[${vatID}].callNow(vat:device:${target.id}=ker:${JSON.stringify( | ||
dev, | ||
)}).${method}`, | ||
); | ||
const ret = invoke(dev, method, argsString, slots); | ||
const retSlots = ret.slots.map(slot => mapInbound(slot)); | ||
return harden({ data: ret.data, slots: retSlots }); | ||
} | ||
function replay(name, ...args) { | ||
@@ -290,7 +543,7 @@ const s = playbackSyscalls.shift(); | ||
}, | ||
fulfillToTarget(...args) { | ||
transcriptAddSyscall(['fulfillToTarget', ...args]); | ||
fulfillToPresence(...args) { | ||
transcriptAddSyscall(['fulfillToPresence', ...args]); | ||
return inReplay | ||
? replay('fulfillToTarget', ...args) | ||
: doFulfillToTarget(...args); | ||
? replay('fulfillToPresence', ...args) | ||
: doFulfillToPresence(...args); | ||
}, | ||
@@ -302,2 +555,8 @@ reject(...args) { | ||
callNow(...args) { | ||
const ret = inReplay ? replay('callNow', ...args) : doCallNow(...args); | ||
transcriptAddSyscall(['callNow', ...args], ret); | ||
return ret; | ||
}, | ||
log(str) { | ||
@@ -321,3 +580,6 @@ log.push(`${str}`); | ||
const dispatch = setup(syscall, state, helpers, devices); | ||
const dispatch = setup(syscall, state, helpers); | ||
if (!dispatch || dispatch.deliver === undefined) { | ||
throw new Error(`vat setup() failed to return a 'dispatch' with .deliver`); | ||
} | ||
@@ -352,6 +614,6 @@ // dispatch handlers: these are used by the kernel core | ||
} | ||
const inputSlots = msg.slots.map(slot => mapInbound(vatID, slot)); | ||
const inputSlots = msg.slots.map(slot => mapInbound(slot)); | ||
const resolverID = | ||
msg.kernelResolverID && | ||
mapInbound(vatID, { type: 'resolver', id: msg.kernelResolverID }).id; | ||
mapInbound({ type: 'resolver', id: msg.kernelResolverID }).id; | ||
return doProcess( | ||
@@ -373,3 +635,3 @@ [ | ||
const { kernelPromiseID, vatID } = message; | ||
const relativeID = mapInbound(vatID, { | ||
const relativeID = mapInbound({ | ||
type: 'resolver', | ||
@@ -386,7 +648,7 @@ id: kernelPromiseID, | ||
const p = kernelPromises.get(kernelPromiseID); | ||
const relativeID = mapInbound(vatID, { | ||
const relativeID = mapInbound({ | ||
type: 'promise', | ||
id: kernelPromiseID, | ||
}).id; | ||
const slots = p.fulfillSlots.map(slot => mapInbound(vatID, slot)); | ||
const slots = p.fulfillSlots.map(slot => mapInbound(slot)); | ||
return doProcess( | ||
@@ -398,13 +660,13 @@ ['notifyFulfillToData', relativeID, p.fulfillData, slots], | ||
if (type === 'notifyFulfillToTarget') { | ||
if (type === 'notifyFulfillToPresence') { | ||
const { kernelPromiseID } = message; | ||
const p = kernelPromises.get(kernelPromiseID); | ||
const relativeID = mapInbound(vatID, { | ||
const relativeID = mapInbound({ | ||
type: 'promise', | ||
id: kernelPromiseID, | ||
}).id; | ||
const slot = mapInbound(vatID, p.fulfillSlot); | ||
const slot = mapInbound(p.fulfillSlot); | ||
return doProcess( | ||
['notifyFulfillToTarget', relativeID, slot], | ||
`vat[${vatID}].promise[${relativeID}] fulfillToTarget failed`, | ||
['notifyFulfillToPresence', relativeID, slot], | ||
`vat[${vatID}].promise[${relativeID}] fulfillToPresence failed`, | ||
); | ||
@@ -416,7 +678,7 @@ } | ||
const p = kernelPromises.get(kernelPromiseID); | ||
const relativeID = mapInbound(vatID, { | ||
const relativeID = mapInbound({ | ||
type: 'promise', | ||
id: kernelPromiseID, | ||
}).id; | ||
const slots = p.rejectSlots.map(slot => mapInbound(vatID, slot)); | ||
const slots = p.rejectSlots.map(slot => mapInbound(slot)); | ||
return doProcess( | ||
@@ -431,2 +693,38 @@ ['notifyReject', relativeID, p.rejectData, slots], | ||
function loadManagerState(vatData) { | ||
if ( | ||
tables.imports.outbound.size || | ||
tables.imports.inbound.size || | ||
tables.promises.inbound.size || | ||
tables.promises.inbound.size || | ||
tables.resolvers.inbound.size || | ||
tables.resolvers.inbound.size | ||
) { | ||
throw new Error(`vat[$vatID] is not empty, cannot loadState`); | ||
} | ||
tables.nextImportID = vatData.nextImportID; | ||
vatData.imports.outbound.forEach(kv => | ||
tables.imports.outbound.set(kv[0], kv[1]), | ||
); | ||
vatData.imports.inbound.forEach(kv => | ||
tables.imports.inbound.set(kv[0], kv[1]), | ||
); | ||
tables.nextPromiseID = vatData.nextPromiseID; | ||
vatData.promises.outbound.forEach(kv => | ||
tables.promises.outbound.set(kv[0], kv[1]), | ||
); | ||
vatData.promises.inbound.forEach(kv => | ||
tables.promises.inbound.set(kv[0], kv[1]), | ||
); | ||
tables.nextResolverID = vatData.nextResolverID; | ||
vatData.resolvers.outbound.forEach(kv => | ||
tables.resolvers.outbound.set(kv[0], kv[1]), | ||
); | ||
vatData.resolvers.inbound.forEach(kv => | ||
tables.resolvers.inbound.set(kv[0], kv[1]), | ||
); | ||
} | ||
async function loadState(savedState) { | ||
@@ -436,2 +734,3 @@ if (!useTranscript) { | ||
} | ||
inReplay = true; | ||
@@ -447,2 +746,24 @@ for (let i = 0; i < savedState.transcript.length; i += 1) { | ||
function getManagerState() { | ||
return { | ||
nextImportID: tables.nextImportID, | ||
imports: { | ||
outbound: Array.from(tables.imports.outbound.entries()), | ||
inbound: Array.from(tables.imports.inbound.entries()), | ||
}, | ||
nextPromiseID: tables.nextPromiseID, | ||
promises: { | ||
outbound: Array.from(tables.promises.outbound.entries()), | ||
inbound: Array.from(tables.promises.inbound.entries()), | ||
}, | ||
nextResolverID: tables.nextResolverID, | ||
resolvers: { | ||
outbound: Array.from(tables.resolvers.outbound.entries()), | ||
inbound: Array.from(tables.resolvers.inbound.entries()), | ||
}, | ||
}; | ||
} | ||
function getCurrentState() { | ||
@@ -452,4 +773,30 @@ return { transcript: Array.from(transcript) }; | ||
const manager = { loadState, processOneMessage, getCurrentState }; | ||
function dumpTables() { | ||
const res = []; | ||
tables.imports.outbound.forEach((target, slot) => { | ||
res.push([vatID, 'import', slot, target.type, target.vatID, target.id]); | ||
}); | ||
tables.promises.outbound.forEach((kernelPromiseID, id) => { | ||
res.push([vatID, 'promise', id, kernelPromiseID]); | ||
}); | ||
tables.resolvers.outbound.forEach((kernelPromiseID, id) => { | ||
res.push([vatID, 'resolver', id, kernelPromiseID]); | ||
}); | ||
kdebug(`vatMAnager.dumpTables ${JSON.stringify(res)}`); | ||
return harden(res); | ||
} | ||
const manager = { | ||
mapInbound, | ||
mapOutbound, | ||
dumpTables, | ||
loadManagerState, | ||
loadState, | ||
processOneMessage, | ||
getManagerState, | ||
getCurrentState, | ||
}; | ||
return manager; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
264947
42
3651
0
13
4