diff --git a/packages/SwingSet/src/kernel/vatTranslator.js b/packages/SwingSet/src/kernel/vatTranslator.js index 0112886cd58..a9343dd725a 100644 --- a/packages/SwingSet/src/kernel/vatTranslator.js +++ b/packages/SwingSet/src/kernel/vatTranslator.js @@ -160,7 +160,7 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) { function insistValidVatstoreKey(key) { assert.typeof(key, 'string'); - assert(key.match(/^[\w.+/]+$/)); + assert(key.match(/^[-\w.+/]+$/)); } function translateVatstoreGet(key) { diff --git a/packages/SwingSet/src/vats/comms/clist-inbound.js b/packages/SwingSet/src/vats/comms/clist-inbound.js index 9f2a14cdca6..b9dca24abce 100644 --- a/packages/SwingSet/src/vats/comms/clist-inbound.js +++ b/packages/SwingSet/src/vats/comms/clist-inbound.js @@ -36,19 +36,15 @@ export function makeInbound(state) { const remote = state.getRemote(remoteID); const lpid = remote.mapFromRemote(flipRemoteSlot(rpid)); remote.deleteToRemoteMapping(lpid); - remote.enqueueRetirement(remote.nextSendSeqNum(), rpid); + remote.enqueueRetirement(rpid); cdebug(`comms begin retiring ${remoteID} ${rpid} ${lpid}`); } function retireAcknowledgedRemotePromiseIDs(remoteID, ackSeqNum) { const remote = state.getRemote(remoteID); - for (;;) { - const rpid = remote.nextReadyRetirement(ackSeqNum); - if (rpid) { - retireRemotePromiseID(remoteID, flipRemoteSlot(rpid)); - } else { - break; - } + const readyToRetire = remote.getReadyRetirements(ackSeqNum); + for (const rpid of readyToRetire) { + retireRemotePromiseID(remoteID, flipRemoteSlot(rpid)); } } diff --git a/packages/SwingSet/src/vats/comms/delivery.js b/packages/SwingSet/src/vats/comms/delivery.js index b6338a222ef..4b66038fd45 100644 --- a/packages/SwingSet/src/vats/comms/delivery.js +++ b/packages/SwingSet/src/vats/comms/delivery.js @@ -136,9 +136,9 @@ export function makeDeliveryKit(state, syscall, transmit, clistKit) { assert(delim1 >= 0, X`received message ${message} lacks seqNum delimiter`); const seqNum = message.substring(0, delim1); const remote = state.getRemote(remoteID); - remote.advanceReceivedSeqNum(); + const recvSeqNum = remote.advanceReceivedSeqNum(); assert( - seqNum === '' || seqNum === `${remote.lastReceivedSeqNum()}`, + seqNum === '' || seqNum === `${recvSeqNum}`, X`unexpected recv seqNum ${seqNum}`, ); diff --git a/packages/SwingSet/src/vats/comms/dispatch.js b/packages/SwingSet/src/vats/comms/dispatch.js index d18d8b6d98a..6cd2a045435 100644 --- a/packages/SwingSet/src/vats/comms/dispatch.js +++ b/packages/SwingSet/src/vats/comms/dispatch.js @@ -18,7 +18,7 @@ export function buildCommsDispatch( vatParameters = {}, ) { const { identifierBase = 0, sendExplicitSeqNums = true } = vatParameters; - const state = makeState(null, identifierBase); + const state = makeState(syscall, identifierBase); const clistKit = makeCListKit(state, syscall); function transmit(remoteID, msg) { @@ -41,10 +41,20 @@ export function buildCommsDispatch( // our root object (o+0) is the Comms Controller const controller = makeVatSlot('object', true, 0); - state.addMetaObject(controller); - cdebug(`comms controller is ${controller}`); + + let needToInitializeState = true; + + function initializeState() { + state.initialize(); + state.addMetaObject(controller); + cdebug(`comms controller is ${controller}`); + needToInitializeState = false; + } function deliver(target, method, args, result) { + if (needToInitializeState) { + initializeState(); + } // console.debug(`comms.deliver ${target} r=${result}`); insistCapData(args); diff --git a/packages/SwingSet/src/vats/comms/remote.js b/packages/SwingSet/src/vats/comms/remote.js index 512b2e8af43..908bb5f88d0 100644 --- a/packages/SwingSet/src/vats/comms/remote.js +++ b/packages/SwingSet/src/vats/comms/remote.js @@ -85,8 +85,10 @@ export function makeRemote(store, remoteID) { function advanceReceivedSeqNum() { const key = `${remoteID}.recvSeq`; - const seqNum = Number(store.getRequired(key)); - store.set(key, `${seqNum + 1}`); + let seqNum = Number(store.getRequired(key)); + seqNum += 1; + store.set(key, `${seqNum}`); + return seqNum; } function allocateRemoteObject() { @@ -113,25 +115,30 @@ export function makeRemote(store, remoteID) { return makeRemoteSlot('promise', false, index); } - function enqueueRetirement(seqNum, rpid) { - const key = `${remoteID}.rq`; - const retirementQueue = JSON.parse(store.getRequired(key)); + function enqueueRetirement(rpid) { + const seqNum = nextSendSeqNum(); + const queueKey = `${remoteID}.rq`; + const retirementQueue = JSON.parse(store.getRequired(queueKey)); retirementQueue.push([seqNum, rpid]); - store.set(key, JSON.stringify(retirementQueue)); + store.set(queueKey, JSON.stringify(retirementQueue)); } - function nextReadyRetirement(ackSeqNum) { + function getReadyRetirements(ackSeqNum) { const key = `${remoteID}.rq`; const retirementQueue = JSON.parse(store.getRequired(key)); - if (retirementQueue.length > 0) { + const ready = []; + while (retirementQueue.length > 0) { const [sentSeqNum, rpid] = retirementQueue[0]; - if (sentSeqNum <= ackSeqNum) { - retirementQueue.shift(); - store.set(key, JSON.stringify(retirementQueue)); - return rpid; + if (sentSeqNum > ackSeqNum) { + break; } + ready.push(rpid); + retirementQueue.shift(); + } + if (ready.length > 0) { + store.set(key, JSON.stringify(retirementQueue)); } - return undefined; + return ready; } return harden({ @@ -151,6 +158,6 @@ export function makeRemote(store, remoteID) { lastReceivedSeqNum, advanceReceivedSeqNum, enqueueRetirement, - nextReadyRetirement, + getReadyRetirements, }); } diff --git a/packages/SwingSet/src/vats/comms/state.js b/packages/SwingSet/src/vats/comms/state.js index fca5af8a454..5e052f456c6 100644 --- a/packages/SwingSet/src/vats/comms/state.js +++ b/packages/SwingSet/src/vats/comms/state.js @@ -9,25 +9,34 @@ import { cdebug } from './cdebug'; const COMMS = 'comms'; const KERNEL = 'kernel'; -function makeEphemeralVatstore() { - const store = new Map(); +function makeEphemeralSyscallVatstore() { + console.log('making fake vatstore'); + const map = new Map(); + return harden({ + vatstoreGet: key => map.get(key), + vatstoreSet: (key, value) => map.set(key, value), + vatstoreDelete: key => map.delete(key), + }); +} + +function makeSyscallStore(syscall) { return harden({ get(key) { assert.typeof(key, 'string'); - return store.get(key); + return syscall.vatstoreGet(key); }, set(key, value) { assert.typeof(key, 'string'); assert.typeof(value, 'string'); - store.set(key, value); + syscall.vatstoreSet(key, value); }, delete(key) { assert.typeof(key, 'string'); - return store.delete(key); + return syscall.vatstoreDelete(key); }, getRequired(key) { assert.typeof(key, 'string'); - const result = store.get(key); + const result = syscall.vatstoreGet(key); assert(result !== undefined, X`store lacks required key ${key}`); return result; }, @@ -57,7 +66,7 @@ function commaSplit(s) { // record the new `p+NN` value anywhere. The counter we use for allocation // will continue on to the next higher NN. -export function makeState(store, identifierBase = 0) { +export function makeState(syscall, identifierBase = 0) { // Comms vat state is kept in the vatstore, which is managed by the kernel and // accessed as part of the syscall interface. The schema used here is very // similar to (in fact, modelled upon) the schema the kernel uses for its own @@ -90,7 +99,9 @@ export function makeState(store, identifierBase = 0) { // r.nextID = $NN // remote connection identifier allocation counter (rNN) // r.$loid = r$NN // mapping receiver objects to remotes they receive from // rname.$name = r$NN // mapping from remote names to remote IDs + // (remote name strings are limited to sequences of [A-Za-z0-9.+-] ) // r$NN.initialized = true // present if this remote has had its storage initialized + // r$NN.name = name // name for this remote // r$NN.transmitterID = $kfref // transmitter object for sending to remote // r$NN.c.$rref = $lref // r$NN inbound c-list (ro+NN/ro-NN/rp+NN/rp-NN -> loNN/lpNN) // r$NN.c.$lref = $rref // r$NN outbound c-list (loNN/lpNN -> ro+NN/ro-NN/rp+NN/rp-NN) @@ -100,18 +111,21 @@ export function makeState(store, identifierBase = 0) { // r$NN.p.nextID = $NN // r$NN promise identifier allocation counter (rp-NN) // r$NN.rq = [[$seqnum,$rpid],[$seqnum,$rpid]...] // r$NN promise retirement queue - if (!store) { - store = makeEphemeralVatstore(); + if (!syscall) { + syscall = makeEphemeralSyscallVatstore(); } + const store = makeSyscallStore(syscall); - function createStartingCommsVatState() { - store.set('identifierBase', `${identifierBase}`); - store.set('lo.nextID', `${identifierBase + 10}`); - store.set('lp.nextID', `${identifierBase + 20}`); - store.set('o.nextID', `${identifierBase + 30}`); - store.set('p.nextID', `${identifierBase + 40}`); - store.set('r.nextID', '1'); - store.set('initialized', 'true'); + function initialize() { + if (!store.get('initialized')) { + store.set('identifierBase', `${identifierBase}`); + store.set('lo.nextID', `${identifierBase + 10}`); + store.set('lp.nextID', `${identifierBase + 20}`); + store.set('o.nextID', `${identifierBase + 30}`); + store.set('p.nextID', `${identifierBase + 40}`); + store.set('r.nextID', '1'); + store.set('initialized', 'true'); + } } function mapFromKernel(kfref) { @@ -320,7 +334,7 @@ export function makeState(store, identifierBase = 0) { } function addRemote(name, transmitterID) { - assert(/^\w+$/.test(name), X`not a valid remote name: ${name}`); + assert(/^[-\w.+]+$/.test(name), `not a valid remote name: ${name}`); const nameKey = `rname.${name}`; assert(!store.get(nameKey), X`remote name ${name} already in use`); @@ -354,11 +368,9 @@ export function makeState(store, identifierBase = 0) { return store.get(`r.${receiverID}`); } - if (!store.get('initialized')) { - createStartingCommsVatState(); - } - return harden({ + initialize, + mapFromKernel, mapToKernel, addKernelMapping, diff --git a/packages/SwingSet/test/commsVatDriver.js b/packages/SwingSet/test/commsVatDriver.js index b75647a2e7e..af3f8e3fac2 100644 --- a/packages/SwingSet/test/commsVatDriver.js +++ b/packages/SwingSet/test/commsVatDriver.js @@ -148,6 +148,7 @@ const oCommsRoot = '@o+0'; // Always the root of the comms vat * @returns {unknown} a syscall object */ function loggingSyscall(log) { + const fakestore = new Map(); return harden({ send(target, method, args, result) { // console.log(`<< send ${target}, ${method}, ${JSON.stringify(args)}, ${result}`); @@ -161,6 +162,15 @@ function loggingSyscall(log) { // console.log(`<< subscribe ${slot}`); log.push(slot); }, + vatstoreGet(key) { + return fakestore.get(key); + }, + vatstoreSet(key, value) { + fakestore.set(key, value); + }, + vatstoreDelete(key) { + fakestore.delete(key); + }, }); } diff --git a/packages/SwingSet/test/test-comms.js b/packages/SwingSet/test/test-comms.js index ee3471021ba..1b3d8259b72 100644 --- a/packages/SwingSet/test/test-comms.js +++ b/packages/SwingSet/test/test-comms.js @@ -9,6 +9,7 @@ import { commsVatDriver } from './commsVatDriver'; test('provideRemoteForLocal', t => { const s = makeState(null, 0); + s.initialize(); const fakeSyscall = {}; const clistKit = makeCListKit(s, fakeSyscall); const { provideRemoteForLocal } = clistKit; @@ -23,12 +24,22 @@ test('provideRemoteForLocal', t => { function mockSyscall() { const sends = []; + const fakestore = new Map(); const syscall = harden({ send(targetSlot, method, args) { sends.push([targetSlot, method, args]); return 'r-1'; }, subscribe(_targetSlot) {}, + vatstoreGet(key) { + return fakestore.get(key); + }, + vatstoreSet(key, value) { + fakestore.set(key, value); + }, + vatstoreDelete(key) { + fakestore.delete(key); + }, }); return { syscall, sends }; } @@ -47,6 +58,7 @@ test('transmit', t => { const { syscall, sends } = mockSyscall(); const d = buildCommsDispatch(syscall, 'fakestate', 'fakehelpers'); const { state, clistKit } = debugState.get(d); + state.initialize(); const { provideKernelForLocal, provideLocalForKernel, @@ -118,6 +130,7 @@ test('receive', t => { const { syscall, sends } = mockSyscall(); const d = buildCommsDispatch(syscall, 'fakestate', 'fakehelpers'); const { state, clistKit } = debugState.get(d); + state.initialize(); const { provideLocalForKernel, getKernelForLocal,