Skip to content

Commit

Permalink
refactor(daemon): Move mail delivery to agent public facets (#2186)
Browse files Browse the repository at this point in the history
Toward delivering mail over the network, this change introduces a
`deliver` method on guest and host agents that accepts arbitrary
messages into the agent’s inbox. Any other agent can send mail to any
other local agent for whom they have a handle and cannot spoof their
sender handle.

This is a net simplification, since it reduces our dependence on the
internal facet of agents and requires less machinery because it treats
messages as capabilities on the wire, freely exposing and carrying
identifiers. We no longer have separate internal and external
representations of messages and pet name “dubbing” is deferred to the
UI. This in turn enables us to defer to the UI the question of whether
to query the name for each identifier once or watch for changes.

Stacked on #2184
  • Loading branch information
kriskowal authored Apr 11, 2024
2 parents 983900d + af50aec commit d650464
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 412 deletions.
79 changes: 60 additions & 19 deletions packages/cli/demo/cat.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
// the request will exit with the number 42.

/* global window document */
/* eslint-disable no-continue */

import { E } from '@endo/far';
import { makeRefIterator } from '@endo/daemon/ref-reader.js';
Expand All @@ -31,16 +32,27 @@ const dateFormatter = new window.Intl.DateTimeFormat(undefined, {
});

const inboxComponent = async ($parent, $end, powers) => {
const selfId = await E(powers).identify('SELF');
for await (const message of makeRefIterator(E(powers).followMessages())) {
const { number, who, when, dismissed } = message;
const { number, type, from: fromId, to: toId, date, dismissed } = message;

let verb = '';
if (type === 'request') {
verb = 'requested';
} else if (type === 'package') {
verb = 'sent';
} else {
verb = 'sent an unrecognizable message';
}
const $verb = document.createElement('em');
$verb.innerText = verb;

const $message = document.createElement('div');

const $error = document.createElement('span');
$error.style.color = 'red';
$error.innerText = '';
// To be inserted later, but declared here for reference.

const $message = document.createElement('div');
$parent.insertBefore($message, $end);
$message.appendChild($error);

dismissed.then(() => {
$message.remove();
Expand All @@ -50,20 +62,49 @@ const inboxComponent = async ($parent, $end, powers) => {
$number.innerText = `${number}. `;
$message.appendChild($number);

const $who = document.createElement('b');
$who.innerText = `${who}:`;
$message.appendChild($who);
if (fromId === selfId && toId === selfId) {
$message.appendChild(verb);
} else if (fromId === selfId) {
const toName = await E(powers).reverseIdentify(toId);
if (toName === undefined) {
continue;
}
const $to = document.createElement('strong');
$to.innerText = ` ${toName} `;
$message.appendChild($verb);
$message.appendChild($to);
} else if (toId === selfId) {
const fromName = await E(powers).reverseIdentify(fromId);
if (fromName === undefined) {
continue;
}
const $from = document.createElement('strong');
$from.innerText = ` ${fromName} `;
$message.appendChild($from);
$message.appendChild($verb);
} else {
const [fromName, toName] = await Promise.all(
[fromId, toId].map(id => E(powers).reverseIdentify(id)),
);
const $from = document.createElement('strong');
$from.innerText = ` ${fromName} `;
const $to = document.createElement('strong');
$to.innerText = ` ${toName} `;
$message.appendChild($from);
$message.appendChild($verb);
$message.appendChild($to);
}

if (message.type === 'request') {
const { what, settled } = message;
const { description, settled } = message;

const $what = document.createElement('span');
$what.innerText = ` ${what} `;
$message.appendChild($what);
const $description = document.createElement('span');
$description.innerText = ` ${JSON.stringify(description)} `;
$message.appendChild($description);

const $when = document.createElement('i');
$when.innerText = dateFormatter.format(Date.parse(when));
$message.appendChild($when);
const $date = document.createElement('i');
$date.innerText = dateFormatter.format(Date.parse(date));
$message.appendChild($date);

const $input = document.createElement('span');
$message.appendChild($input);
Expand Down Expand Up @@ -124,9 +165,9 @@ const inboxComponent = async ($parent, $end, powers) => {

$message.appendChild(document.createTextNode('" '));

const $when = document.createElement('i');
$when.innerText = dateFormatter.format(Date.parse(when));
$message.appendChild($when);
const $date = document.createElement('i');
$date.innerText = dateFormatter.format(Date.parse(date));
$message.appendChild($date);

$message.appendChild(document.createTextNode(' '));

Expand Down Expand Up @@ -179,7 +220,7 @@ const inboxComponent = async ($parent, $end, powers) => {
});
};

$message.appendChild($error);
$parent.insertBefore($message, $end);
}
};

Expand Down
60 changes: 46 additions & 14 deletions packages/cli/src/commands/inbox.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,73 @@
/* global process */
/* eslint-disable no-continue */

import os from 'os';
import { E } from '@endo/far';
import { makeRefIterator } from '@endo/daemon';
import { withEndoAgent } from '../context.js';
import { formatMessage } from '../message-format.js';

const { stringify: q } = JSON;

export const inbox = async ({ follow, agentNames }) =>
withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
const selfId = await E(agent).identify('SELF');
const messages = follow
? makeRefIterator(E(agent).followMessages())
: await E(agent).listMessages();
for await (const message of messages) {
const { number, who, when } = message;
const { number, type, from, to, date } = message;

let verb = '';
if (type === 'request') {
verb = 'requested';
} else if (type === 'package') {
verb = 'sent';
} else {
verb = 'sent an unrecognizable message';
}

let provenance = 'unrecognizable message';
if (from === selfId && to === selfId) {
provenance = `you ${verb} yourself `;
} else if (from === selfId) {
const [toName] = await E(agent).reverseIdentify(to);
if (toName === undefined) {
continue;
}
provenance = `${verb} ${q(toName)} `;
} else if (to === selfId) {
const [fromName] = await E(agent).reverseIdentify(from);
if (fromName === undefined) {
continue;
}
provenance = `${q(fromName)} ${verb} `;
} else {
const [toName] = await E(agent).reverseIdentify(to);
const [fromName] = await E(agent).reverseIdentify(from);
if (fromName === undefined || toName === undefined) {
continue;
}
provenance = `${q(fromName)} ${verb} ${q(toName)} `;
}

if (message.type === 'request') {
const { what } = message;
const { description } = message;
console.log(
`${number}. ${JSON.stringify(who)} requested ${JSON.stringify(
what,
)} at ${JSON.stringify(when)}`,
`${number}. ${provenance}${JSON.stringify(
description,
)} at ${JSON.stringify(date)}`,
);
} else if (message.type === 'package') {
const { strings, names: edgeNames } = message;
console.log(
`${number}. ${JSON.stringify(who)} sent ${formatMessage(
`${number}. ${provenance}${formatMessage(
strings,
edgeNames,
)} at ${JSON.stringify(when)}`,
)} at ${JSON.stringify(date)}`,
);
} else {
console.log(
`${number}. ${JSON.stringify(
who,
)} sent an unrecognizable message at ${JSON.stringify(
when,
)}. Consider upgrading.`,
);
console.log(`${number}. ${provenance}, consider upgrading.`);
}
}
});
2 changes: 1 addition & 1 deletion packages/cli/src/commands/show.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import { withEndoAgent } from '../context.js';

export const show = async ({ name, agentNames }) =>
withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
const pet = await E(agent).lookup(name);
const pet = await E(agent).lookup(...name.split('.'));
console.log(pet);
});
16 changes: 1 addition & 15 deletions packages/daemon/src/daemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -890,16 +890,6 @@ const makeDaemonCore = async (
});
};

/** @type {import('./types.js').DaemonCore['provideControllerAndResolveHandle']} */
const provideControllerAndResolveHandle = async id => {
const handle = /** @type {{}} */ (await provide(id));
const agentId = agentIdForHandle.get(handle);
if (agentId === undefined) {
throw assert.error(assert.details`No agent for handle ${id}`);
}
return provideController(agentId);
};

/** @type {import('./types.js').DaemonCore['formulateReadableBlob']} */
const formulateReadableBlob = async (readerRef, deferredTasks) => {
const { formulaNumber, contentSha512 } = await formulaGraphJobs.enqueue(
Expand Down Expand Up @@ -1578,14 +1568,10 @@ const makeDaemonCore = async (
formulateDirectory,
});

const makeMailbox = makeMailboxMaker({
provide,
provideControllerAndResolveHandle,
});
const makeMailbox = makeMailboxMaker({ provide });

const makeIdentifiedGuestController = makeGuestMaker({
provide,
provideControllerAndResolveHandle,
makeMailbox,
makeDirectoryNode,
});
Expand Down
37 changes: 7 additions & 30 deletions packages/daemon/src/guest.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,10 @@ import { makePetSitter } from './pet-sitter.js';
/**
* @param {object} args
* @param {import('./types.js').DaemonCore['provide']} args.provide
* @param {import('./types.js').DaemonCore['provideControllerAndResolveHandle']} args.provideControllerAndResolveHandle
* @param {import('./types.js').MakeMailbox} args.makeMailbox
* @param {import('./types.js').MakeDirectoryNode} args.makeDirectoryNode
*/
export const makeGuestMaker = ({
provide,
provideControllerAndResolveHandle,
makeMailbox,
makeDirectoryNode,
}) => {
export const makeGuestMaker = ({ provide, makeMailbox, makeDirectoryNode }) => {
/**
* @param {string} guestId
* @param {string} handleId
Expand Down Expand Up @@ -49,25 +43,16 @@ export const makeGuestMaker = ({
SELF: handleId,
HOST: hostHandleId,
});
const hostController =
/** @type {import('./types.js').EndoHostController} */
(await provideControllerAndResolveHandle(hostHandleId));
const hostPrivateFacet = await hostController.internal;
const { respond: deliverToHost } = hostPrivateFacet;
if (deliverToHost === undefined) {
throw new Error(
`panic: a host request function must exist for every host`,
);
}

const mailbox = makeMailbox({
petStore: specialStore,
selfId: handleId,
context,
});
const { petStore } = mailbox;
const { petStore, handle } = mailbox;
const directory = makeDirectoryNode(petStore);

const { reverseIdentify } = specialStore;
const {
has,
identify,
Expand All @@ -92,23 +77,15 @@ export const makeGuestMaker = ({
dismiss,
request,
send,
receive,
respond,
deliver,
} = mailbox;

const handle = makeExo(
'EndoGuestHandle',
M.interface('EndoGuestHandle', {}),
{},
);

/** @type {import('./types.js').EndoGuest} */
const guest = {
// Agent
handle: () => handle,
// Directory
has,
identify,
reverseIdentify,
locate,
list,
listIdentifiers,
Expand All @@ -121,6 +98,7 @@ export const makeGuestMaker = ({
copy,
makeDirectory,
// Mail
handle,
listMessages,
followMessages,
resolve,
Expand All @@ -129,6 +107,7 @@ export const makeGuestMaker = ({
dismiss,
request,
send,
deliver,
};

const external = makeExo(
Expand All @@ -141,8 +120,6 @@ export const makeGuestMaker = ({
},
);
const internal = harden({
receive,
respond,
petStore,
});

Expand Down
Loading

0 comments on commit d650464

Please sign in to comment.