@erights @dtribble @Chris-Hibbert and I had a long discussion this morning about three-party handoff and WormholeOp and the "lost resolution bug". I won't be able to capture the full idea here, but one of the conclusions was that maybe we should change the swingset kernel to forbid passing Presences (i.e. objects) in the arguments of message sends (syscall.send()). They would be allowed as the target of syscall.send(), and in the arguments of syscall.resolve(), and could appear in dispatch.notify(). But they would never appear in the arguments of dispatch.notify(), and it would be a fatal error (terminating the vat) to include them in the arguments of syscall.send().
The rough issue is that three-party introductions between distant SwingSet machines are not synchronous. When Alice does bob!foo(carol), sending a Presence for carol to a Vat that does not yet have its own reference to carol, the message will arrive on Bob's Vat with instructions for how to contact Carol's vat, and an ID for some sort of handoff table entry on Carol. That will take a while to exercise, so Bob's vat won't have confirmation of the object's identity until Carol's vat responds. Since we want Presences to be canonical and comparable by identity, we (bob's vat) can't deliver/dispatch that foo message until after carol responds. But Alice may have sent another message right behind the foo, and we don't want Carol's vat to be able to deny liveness of messages that don't reference her. But we also want Alice's messages to be delivered in the order that she sent them.
We don't yet know how to deal with this for remote swingset machines, so we might want to forbid this case when the reference spans multiple machines. We can handle it for multiple vats within a single SwingSet machine, but it wouldn't be easy for a programmer to know when they can rely upon this (it kind of creates a new class of reference: Near, Far, and Further). So for consistency, maybe we should forbid it in all cases, not just the ones that involve multiple machines.
There are a couple of compromises that might help. One would be to make a rule that everything arrives as a promise: both presences and promises. Then we could deliver foo(carolPromise) just fine. Bob would have to wait for the promise to resolve (i.e. carolPromise.then(carolPresence => something()) before he could do any identity comparisons. But:
programmer confusion: one piece of code clearly sends a presence, but the matching piece of code must tolerate a different type appearing. This would occur inside structures too: the ERTP Amount record includes a number and an Issuer, and the Issuer is a Presence that can be compared by identity. Doing a .then() on the overall record isn't enough: you have to look inside and find every piece that might have been a Presence and do a separate .then() on it.
a lot of the ERTP ordering behavior (eg deposit-before-withdrawal) depends upon knowing that you won't have to wait a turn before the arguments are ready for use
Another idea was to use Dean's Flow mechanism. Basically we make a rule that the messages on a given Flow will block until the referent responds (but messages on unrelated Flows continue unimpeded). Alice would be explicitly opting-in to having her second message block (allowing Carol's vat to delay it indefinitely) by putting it into the same Flow as the one which included a Presence to Carol.
A weak idea, that we rejected almost immediately, was to introduce another class of Presence. You'd have a Promise (on which you can queue messages, but not compare for identity), a Presence (that you get by resolving a Promise, or could receive in an argument), and then a super-Presence (that you can compare for identity, but the only way to get one is to wait a turn). There didn't seem to be a programmer-useful difference between the Promise and the Presence, so we didn't pursue it further.
Restricting programmers to only sending Promises as arguments makes the ERTP ordering behavior harder to manage, but it could be mitigate slightly with MarkM's antiResolve proposal, which we renamed Promise.now() for discussion purposes. This would be like a synchronous form of .then(): if the Promise is currently resolved, it returns the resolution, else it returns undefined. ERTP, on the receiving side, would reject transactions for which the arguments were not "now-able". This still puts the burden on the programmer of the sending side, but relieves the kernel from enforcing the no-Presence checks. However MarkM said the ERTP Amount records, that include an Issuer Presence, made this sound pretty painful.
The Flow thing might work in the longer run, but if we forbid passing Presences today, then adding Flows in the future would let us convert an error case into a working case, which is not a bad transition to make. Whereas allowing them in intra-machine messages but having them cause errors in inter-machine messages would be pretty confusion. "If you can't do something right, don't do it at all".
@erights @dtribble @Chris-Hibbert and I had a long discussion this morning about three-party handoff and WormholeOp and the "lost resolution bug". I won't be able to capture the full idea here, but one of the conclusions was that maybe we should change the swingset kernel to forbid passing Presences (i.e. objects) in the arguments of message sends (
syscall.send()
). They would be allowed as the target ofsyscall.send()
, and in the arguments ofsyscall.resolve()
, and could appear indispatch.notify()
. But they would never appear in the arguments ofdispatch.notify()
, and it would be a fatal error (terminating the vat) to include them in the arguments ofsyscall.send()
.The rough issue is that three-party introductions between distant SwingSet machines are not synchronous. When Alice does
bob!foo(carol)
, sending a Presence forcarol
to a Vat that does not yet have its own reference tocarol
, the message will arrive on Bob's Vat with instructions for how to contact Carol's vat, and an ID for some sort of handoff table entry on Carol. That will take a while to exercise, so Bob's vat won't have confirmation of the object's identity until Carol's vat responds. Since we want Presences to be canonical and comparable by identity, we (bob's vat) can't deliver/dispatch thatfoo
message until after carol responds. But Alice may have sent another message right behind thefoo
, and we don't want Carol's vat to be able to deny liveness of messages that don't reference her. But we also want Alice's messages to be delivered in the order that she sent them.We don't yet know how to deal with this for remote swingset machines, so we might want to forbid this case when the reference spans multiple machines. We can handle it for multiple vats within a single SwingSet machine, but it wouldn't be easy for a programmer to know when they can rely upon this (it kind of creates a new class of reference: Near, Far, and Further). So for consistency, maybe we should forbid it in all cases, not just the ones that involve multiple machines.
There are a couple of compromises that might help. One would be to make a rule that everything arrives as a promise: both presences and promises. Then we could deliver
foo(carolPromise)
just fine. Bob would have to wait for the promise to resolve (i.e.carolPromise.then(carolPresence => something())
before he could do any identity comparisons. But:Amount
record includes a number and anIssuer
, and the Issuer is a Presence that can be compared by identity. Doing a.then()
on the overall record isn't enough: you have to look inside and find every piece that might have been a Presence and do a separate.then()
on it.Another idea was to use Dean's
Flow
mechanism. Basically we make a rule that the messages on a given Flow will block until the referent responds (but messages on unrelated Flows continue unimpeded). Alice would be explicitly opting-in to having her second message block (allowing Carol's vat to delay it indefinitely) by putting it into the same Flow as the one which included a Presence to Carol.A weak idea, that we rejected almost immediately, was to introduce another class of Presence. You'd have a Promise (on which you can queue messages, but not compare for identity), a Presence (that you get by resolving a Promise, or could receive in an argument), and then a super-Presence (that you can compare for identity, but the only way to get one is to wait a turn). There didn't seem to be a programmer-useful difference between the Promise and the Presence, so we didn't pursue it further.
Restricting programmers to only sending Promises as arguments makes the ERTP ordering behavior harder to manage, but it could be mitigate slightly with MarkM's antiResolve proposal, which we renamed
Promise.now()
for discussion purposes. This would be like a synchronous form of.then()
: if the Promise is currently resolved, it returns the resolution, else it returnsundefined
. ERTP, on the receiving side, would reject transactions for which the arguments were not "now
-able". This still puts the burden on the programmer of the sending side, but relieves the kernel from enforcing the no-Presence checks. However MarkM said the ERTPAmount
records, that include anIssuer
Presence, made this sound pretty painful.The Flow thing might work in the longer run, but if we forbid passing Presences today, then adding Flows in the future would let us convert an error case into a working case, which is not a bad transition to make. Whereas allowing them in intra-machine messages but having them cause errors in inter-machine messages would be pretty confusion. "If you can't do something right, don't do it at all".