Closed warner closed 2 years ago
@gibson042 I think I'm going to have you work on at least part of this task.. even if only the mapping from remote ID to transmitter/receiver objects were durable, that'd help us in the case of a future upgrade. We'll talk tomorrow about the details.
@gibson042 so https://github.com/Agoric/agoric-sdk/blob/master/packages/SwingSet/src/vats/vattp/vat-vattp.js is the code that needs updating. Here's a copy of the vat-timer work I've started today, that might be useful as a template (although it almost certainly doesn't work yet):
// @ts-check
import { Nat } from '@agoric/nat';
import { assert, details as X } from '@agoric/assert';
import { Far, passStyleOf } from '@endo/marshal';
import { makeNotifierFromAsyncIterable } from '@agoric/notifier';
import { makePromiseKit } from '@endo/promise-kit';
import { makeTimedIterable } from './timed-iteration.js';
import { provideKindHandle, defineDurableKind } from '@agoric/vat-data';
export function buildRootObject(vatPowers, _vatParameters, baggage) {
const { D } = vatPowers;
const repeaters = new Map();
const serviceHandle = provideKindHandle(baggage, 'timerService');
/**
* @typedef {bigint} Time
* @typedef {unknown} Handler
* @typedef {unknown} CancelToken
* @typedef { { handler: Handler, cancel: CancelToken } } HandlerEntry
* @typedef { { handlers: HandlerEntry[] } } WakeupEntry
* @typedef {MapStore<Time, WakeupEntry>} WakeupTable
*
* @typedef {WeakMapStore<CancelToken, { times: Time[] }>} CancelTable
*
*/
// we rely upon the sortability of keys that are BigInts, and our
// Stores performing efficient iteration
if (!baggage.has('wakeups')) {
baggage.init('wakeups', makeScalarBigMapStore('wakeups', { durable: true }));
}
/** @type {WakeupTable} */
const wakeups = baggage.get('wakeups');
// map cancel handles to the times that hold their events
if (!baggage.has('cancels')) {
baggage.init('cancels', makeScalarBigWeakMapStore('cancels', { durable: true }));
}
/** @type {CancelTable} */
const cancels = baggage.get('removals');
/**
* return list of [ { time, handlers }] for time <= upto
*
* @param {Time} upto
* @typedef { time: Time, handlers: Handler[] } ActionRecord
* @returns { ActionRecord[] }
*/
function removeEventsUpTo(upto) {
const events = [];
for (const [time, entry] of wakeups.entries()) {
if (time <= upto) {
events.push({ time, handlers: entry.handlers });
events.delete(time);
} else {
break;
}
}
return events;
}
/**
* @param {Time} time
* @param {Handler} handler
* @param {CancelToken} cancel[]
*/
function addEvent(time, handler, cancel = undefined) {
const handlerEntry = harden({ handler, cancel });
if (!wakeups.has(time)) {
wakeups.init(time, harden({ handlers: [] }));
}
const entry = wakeups.get(time);
const handlers = entry.handlers.concat([handlerEntry]);
wakeups.set(time, harden({...entry, handlers}));
if (cancel) {
if (cancels.has(cancel)) {
const cancelEntry = cancels.get(cancel);
const { times: oldTimes } = cancelEntry;
if (oldTimes.indexOf(time) !== -1) {
const times = oldTimes.concat(time);
const newEntry = { ...cancelEntry, times };
cancels.set(cancel, harden(newEntry));
}
} else {
const newEntry = { times: [ time ] };
cancels.init(cancel, harden(newEntry));
}
}
}
/**
* @param {CancelToken} cancel
*/
function removeEvent(cancel) {
assert(cancel !== undefined); // that would be super confusing
const cancelEntry = cancels.get(cancel); // might throw
cancels.delete(cancel);
for (const time of cancels.times) {
if (wakeups.has(time)) {
/** @typedef { WakeupEntry } */
const oldEntry = wakeups.get(time);
const { handlers: oldHandlers } = oldEntry;
/** @typedef { HandlerEntry[] } */
const newHandlers = [];
for (const handlerEntry of newHandlers) {
if (handlerEntry.
}
What is the Problem Being Solved?
vat-vattp needs to be upgradable, as part of #5666 .
vattp has two halves. I'm more familiar with the comms routing half than the networking half (and we may need @michaelfig 's help on that side).
The comms routing half is responsible for dispatching messages to/from named remotes. On the input side, the mailbox device sends all inbound messages to a single object. Each message includes both the string "remote ID" of the sender (e.g. an
agoric1234
public-key -based address), the list of pending messages, and the highest ACK sequence number that the sender has seen (of our outgoing messages). It looks the remoteID up in a table to find the vat-commsreceiver
object, filters out duplicate messages (by seqnum), then sends the new ones in separatesyscall.send
s to the comms receiver.On the output side, each remote has a distinct
transmitter
object within vat-vattp. Comms sends individual messages to the transmitter, vat-vattp looks up the remoteID for than transmitter, then vat-vattp invokes the mailbox device to push the message onto the named remote's outbox.vat-vattp also has some wiring APIs: one to teach it about the mailbox device (and vice versa), and another named
addRemote
which causes it to create a newtransmitter
, and to be connected to the vat-comms receiver. There's a short-livedreceiverSetter
object used to finish this handoff.I don't know what the networking half does, but it appears to share the remoteID-keyed table.
Description of the Design
I'm thinking that the remoteID-keyed table should be replaced by a durable Kind, with one instance per
transmitter
. The transmitter objects will have a seqnum or two in theirstate
properties, as well as aremoteID
.The wiring API will need to become a singleton durable Kind. The
receiverSetter
can probably be ephemeral, if we feel safe in assuming that anaddRemote
won't span an upgrade.Security Considerations
All mailbox-based messages are routed through vat-vattp, and it has full authority to route them to whatever transmitter/receiver it wishes, so it has considerable power to subvert (or entirely replace) arbitrary user communication. So the authority to upgrade it must be closely held. (this concern is not specific to vat-vattp, it also applies to comms, and to a lesser extent to vat-vat-admin).
Test Plan
At least careful audit, but ideally (if #5666 also provide a trigger mechanism) we should have unit tests of the actual upgrade process (using a null upgrade).