-
Notifications
You must be signed in to change notification settings - Fork 208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add (immutable) auxiliary data to Presences? #2069
Comments
The Brand, as you mentioned, is the main one. The other uses I've thought about that this would address are alleged type info, and tagging for debugging and tracing. Those don't seem to need live objects.
Yes, though allowing "try to guess" seems likely to be a good way to improve performance in some cases. |
This part confused me because the next sentence talks about pass-by-presence objects. Should this be pass-by-presence? Also, is there a reason to limit the effects to ERTP? What about Zoe? I could see some uses there It seems like the criteria for use are:
If the above criteria are correct, I think we could use it in the following ways in ERTP: Issuerissuer.getAllegedName() - returns a string - yes, this would be helpful Mintmint.getIssuer() - returns an issuer presence - yes, this would be helpful Brandbrand.isMyIssuer() - not helpful, not a getter Pursedeposit - no PaymentgetAllegedBrand - returns a brand presence - yes AmountMathShouldn't be a presence (users should have localAmountMath to preclude a round-trip) |
Oops, you're absolutely right. I'll update the comment.
Nope, no reason to limit it, I should have asked about both (and I'm sure there's lots of contract code which might benefit from this feature).
Yeah, any place where you're currently calling I might add another criteria.. Also, the correctness of the auxilliary data depends upon the source of the Presence. You need to know that the Payment is real before you can believe anything else its data claims. If Carol exports a Remotable with some data, to Alice, and then Alice sends her Presence to Bob, the thing that Bob receives is entirely under Alice's control (both identity and auxilliary data). If Bob receives the same Presence from Carol, then he can believe that both Alice and Carol observe the same auxillary data. Alice cannot deliver an Presence to Bob that is EQ the one he got from Carol but has different auxillary data.
Right, any property whose value is mutable wouldn't qualify for inclusion in aux data.
Yeah those seem like the biggest potential improvements.
This might fall into the category of "desireably lazy".. I'm guessing we always build the Notifier for each Purse, regardless of whether someone calls
Do we think most Purses will have their deposit facets used? Or is this a lesser-used feature. Might fall into the lazy category too.
Is this a case where we really want migratory code? I think AmountMath is basically a set of powerless functions that you want to be able to invoke cheaply/locally/synchronously. In theory I might send you an AmountMath by sending you a string which you then |
@erights and I walked through this today. On the spectrum of "send-every-time" vs "guess" vs "ask", he's inclined to use send-every-time, and then look for a way to optimize things (and I expect @dtribble to prefer "guess", but he's more confident in our ability to guess correctly than me, especially in the face of GC possibly causing one side to forget the data without the other side realizing it). We have two threats to protect against. One is equivocation: the exporting vat (holding the original Remotable) should not be able to present the same object with different auxilliary data to two different parties. The second is replacement: if Alice forwards Carol's object+data to Bob, Alice should not be able to substitute her own data into what Bob gets. To prevent equivocation, we must either ensure that the exporting vat only exports the aux data exactly once (not giving it an opportunity to equivocate), or we must somehow detect that the second+subsequent copies of the data are equivalent to the first. To compare the data against an earlier copy, the easiest approach is probably for the vat to remember the exact capdata it first serialized (probably as an extension of
Another option is for the kernel to demarshal the data received from the vat, and compare it against a demarshalled copy of what the kernel remembered from before, but we both agreed we'd rather keep As a starting point, we would probably allow aux data to include anything that is pass-by-copy, plus Presences (which are Comparable), and excluding Promises entirely. A particularlly interesting idea was to define the identity of an object (its vref) to include the hash of its auxdata. The hope would be that anyone who receives a reference to the object could compare the alleged auxdata against the hash, to make sure they're receiving a correct copy. There are a whole mess of fiddly bits in the way (translation of vrefs into different vrefs by the time it arrives at a subscriber vat, cross-machine refs needing to include a canonical machine ID but chain-based machines have an evolving predicate instead of a single pubkey, separation of swissnum-style access control from c-list access control), but in a different environment the approach could be pretty tidy. |
We had another good meeting on this today. What I learned:
I think we can split this up into two pieces: a lower-level facility for one vat to associate auxdata with a Presence (and deliver it correctly to recipient vats), and a higher-level facility for defining that auxdata (on the Remotable) and converting it into pre-specified behavior on the resulting Presence. The lower-level facility takes place in the deliveries and syscalls used by liveslots and the kernel. The higher-level facility lives somewhere between Lower-level auxdata facilityI can see two rough categories of approaches to the lower-level facility:
We have three properties of interest:
Also, it's worth pointing out that the current use case only needs "pure" auxdata: deeply pass-by-copy (so no Presences). The Brand would carry auxdata containing the String name of the amount math type, rather than e.g. a Presence that represents the amount math type. @erights said we don't currently have a need for Presence-bearing auxdata, but in the future it will become very important. I had thought that a Purse or Payment having a Auxdata in serialized capdata
In this first category of approaches, we have
Hashing auxdata becomes a significant problem if/when that data is allowed to to contain Presences, because their identifiers change depending upon the context. We do not have universal strong identifiers for Remotables, unfortunately, and the problem is made even more challenging by the mutable+evolving nature of the light-client predicate used to name a blockchain-hosted swingset machine. In a world where each swingset is identified by a stable public key, we could use In these approaches, the kernel is nominally unaware of the auxdata (it's just a new part of the serialized capdata, which the kernel usually ignores. But the kernel does need to be involved to protect integrity. It might be possible to maintain the kernel's ignorance by having all vats keep an internal objectID-to-auxdata mapping, and check for equivocation each time they receive (and deserialize) an object, rather than having the kernel be solely responsible for this check. In this case, the first time an auxdata-bearing object arrives in a vat is the defining occurrence, and establishes the auxdata for that Presence. If the same object-id is named in a subsequent message (perhaps from a different vat) that contains different auxdata, the receiving vat knows that something is wrong. It has no way to tell which copy is correct. It's not clear how the vat ought to surface this uncomfortable realization. Auxdata in separate channelInstead, I think we should make the kernel more aware of auxdata, and manage it outside the serialized capdata. The kernel object table, which currently maps If we wanted to minimize space overhead, especially for large auxdata, we could have the receiver ask for an auxdata it doesn't already remember. E.g. if the kernel does @erights expressed concern about the latency of this approach, both for the sub-millisecond inter-process pipes we're using for the XS vat worker, and significantly moreso for the 10+-second delay between remote machines over IBC. And since we expect auxdata to be small, it would be better to just include the auxdata every time. With enough cleverness and coordination, the two sides of any given link might be able to model the other side's state closely enough to know when/if they have the auxdata, and only pass it when they need it. This would minimize both latency and overhead, but would introduce more coupling between the two sides than I care for. If every delivery includes a copy of the auxdata, maybe we could change the signatures of e.g. In this approach, the kernel would prohibit equivocation by comparing every single field of received auxdata against the new column of the kernel object table. Any disagreement would be a vat-vatal violation. One equivocation that might be allowed would be a vat which exports an object with auxdata-1, waits until everyone else drops it (even the kernel), then re-exports the same object-id with auxdata-2. This probably wouldn't cause any problems, because nobody else could tell that the auxdata had changed. However, a system in which the auxdata was hashed to form the object-id would not even tolerate this. |
@warner, I just talked to @erights, and we agreed that we probably don't need to add the This way, even if we did move the |
high-level facility to add local behavior to PresencesAs I learned this morning, the real delivery of this feature is to add methods to the received Presence based upon the auxdata. I think we need a mechanism whereby the receiver can register collections of methods to add if triggered by a particular auxdata key. Perhaps something like: const helpers = harden({
nat: { add, equal, GTE, .. },
set: { add, equal, GTE, .. },
});
function buildRootObject(vatPowers) {
const { registerAuxdataEnhancer } = vatPowers;
function addMathHelpers(name) {
return helpers[name] || {};
}
registerAuxdataEnhancer('mathHelpersTypeName', addMathHelpers);
..
} The idea would be that any incoming object with auxdata that contains a property named This obviously needs a lot of thought:
I imagine one of the MathHelper use cases is to transform something like: const mathHelperTypeName = await oldBalance.brand~.getMathHelperType();
const math = knownHelpers.get(mathHelperTypeName);
const remaining = math.subtract(oldBalance, withdrawalAmount); into: const remaining = oldBalance.something.subtract(withdrawalAmount); (i.e. if the enhancement is a method of the auxdata-bearing Presence, rather than merely an associated function, then it can take a more active role, and we could remove a somewhat-redundant argument from the call). (or maybe that's most interesting if we're talking about adding behavior to data, rather than adding data to behavior, as @FUDCo pointed out) Of course, this is really wandering into mobile-code territory. We might consider a subset of that which avoided the questions of comity and termination by limiting the code to specific strings pre-registered (by the recipient). origin/exporter -side APIHow should the creator of the Remotable express their interest in adding auxdata, or methods, to any resulting Presences? For simple auxdata that arrived as data properties on the It'd be nice if the Remotable-side API were somehow similar to the Presence-side API. For example, if the signature was
|
Ok, cool, if I understand that correctly, then it might be sufficient to do the |
Yes, I think that's right! |
Ok, lemme write down the options as I see them for this smaller feature: Sender-side API choices1: auxdata is included as non- const rem = harden({
mathType: 'nat',
foo() { return 'oof'; },
});
bob~.hello(rem); In this option, the presence of any 2: auxdata is added only in a const rem = Remotable('interfacename', { mathType: 'nat' }, { foo() { return 'oof'; } }); If we choose this API option, the implementation probably needs to stash this auxdata on the Remotable in an unenumerated tagged-Symbol-named property where the liveslots copy of Receiver-side API options3: auxdata appears as non-Function properties on each const bob = harden({
hello(pres) {
console.log(pres.mathType); // 'nat'
pres~.foo(); // returns Promise for 'oof'
},
}); 4: auxdata is retrieved with a special import { getAuxData } from '@agoric/marshal';
const bob = harden({
hello(pres) {
console.log(getAuxData(pres, 'mathType'); // 'nat'
pres~.foo(); // returns Promise for 'oof'
},
}); If we go this way, we'll probably stash the auxdata in a special unenumerated tagged-Symbol-named property on the |
Implementation options5: 6: somehow include the auxdata as a third entry in capdata ( 7: add a new syscall for vats to deliver auxdata to the kernel I'm inclined to go with 5. If auxdata can include other Presences, then the sender's graph traversal must also follow the Presence->auxdata->Presence edges, and the auxdata might include an arbitrary number of additional Presences beyond those in the normal When we do this for Promises, Chip wrote a helper function (in liveslots) which starts from a Promise, serializes everything reachable (via known resolutions) from that point, and returns a full list of promiseIDs and the capdata they are resolved to. I think we might be able to augment this helper function to know about auxdata as well. We serialize the main object graph, and wind up with two side-lists: incidentally resolved promises (promiseID->resolution), and necessary auxdata (objectIDs->aux capdata). |
In my implementation plan, liveslots will need a way to ask If we present API option 1 (auxdata is provided as normal properties on an otherwise pass-by-reference object), then const rem = harden({
mathType: 'nat',
foo() { return 'oof'; },
});
const capdata = serialize(rem);
assert.deepEqual(capdata, { body: JSON.stringify({QCLASS: 'slot', index: 0}}, slots: 'o+0' });
assert.deepEqual(getAuxDataFor(rem), { mathType: 'nat' }); If we use API option 2 (special arguments to |
At lunch @dtribble argued for only sending the auxdata when it is known to be necessary. For the vat-to-kernel direction, this means when liveslots allocates an export-side ( For the kernel-to-vat direction, it means when the kernel first adds the kref into the clist and allocates the import-side ( For the comms protocol, it's when the remote-object-ref (rref) is allocated in the per-remote clist. And the deallocation protocol is all the more important, because this is the one protocol where messages can cross on the wire. |
The main point is that there are many more references to the object that have properties than there are objects. For example, every Amount passed will point at it's Brand. There could be billions of Amounts passed for mere hundreds of brands because the usages are a different cardinality. Therefore we should minimize the cost of reference is possible. Since we should architecturally eliminate any race between the kernel and the vats that it is managing, this should be relatively straightforward in all cases. Otherwise it's non-deterministic what references the kernel has, and we just went through a lot of design work to address that issue. So e.g.,:
The "export tells kernel the reference is no longer exported" SHOULD be (or at least could be) in the form of a single ACK for all the dropped references from the kernel that have not been reused. |
I just spoke with @erights , he said API options 1+3 are ok (mixing Function and data/non-Function properties on both Remotables and Presences). The counterargument is that this makes it harder to introduce a "bare Functions are pass-by-reference" feature in the future, because then how do we distinguish between an all-methods pass-by-reference Remotable and a pass-by-copy record that happens to contain a bunch of individually-referenceable pass-by-reference bare functions? The answer may be straightforward: we require Far() or Remotable() to mark an object as pass-by-reference, so Implementation-wise, he's tentatively in support of the registered-symbol property approach, but we need @michaelfig to weigh in. We don't currently have a way to let liveslots and the user-level vat code get the same instance of the @erights also said that they came up with a workaround in the Zoe meeting, such that we may not need to implement auxdata right away, so maybe we can put some more design work into it first. I'm going to prioritize #2018 over auxdata as a result. |
In talking with @erights today about how pass-by-reference objects with non-trivial prototypes should be treated, we speculated that it might be interesting to allow auxdata to include the prototype, as well as properties. If a pass-by-reference object had a pass-by-copy prototype, what would that mean? Or, if we find a way to add local behavior as auxdata, we send a pass-by-ref object whose prototype was another pass-by-ref object, then the local behavior on the two received Presences would obey the usual inheritance rules. And if we also incorporated local state into the mix, things could get really interesting. |
In our meeting today, @erights mentioned that he's got plans for auxdata that will require it to include Promises. I've been hoping to avoid Promises in auxdata, because we retire the Promise's We speculated that we might serialize these Promises (ones referenced by auxdata) differently, with a marker that says "don't retire this |
Does auxdata+GC enable nondeterminism?How will auxdata and GC-able Presences interact? Vats are not supposed to be able to sense GC actions. Could the presence of auxdata be used as a source of nondeterminism? Our general position is that pass-by-copy data lacks identity: userspace should not be surprised if two independent deliveries of equivalent data appear as distinct JavaScript objects. We say "don't depend upon this", but we cannot prevent userspace from trying to sense it anyways. In practice, we serialize message arguments and promise resolutions in a batch, so Pass-by-reference values do have identity, and userspace correctly depends upon this being reflected by JS object identity. Liveslots is responsible for maintaining this equivalence: to the extent that userspace can tell, any time a given vref (like Since we also want to be able to free up unused objects, liveslots cannot afford to keep the original Presence around forever. So we must lean on the "to the extent that userspace can tell" condition. Liveslots is allowed to drop the Presence as long as userspace can never tell the difference. In the current (#2615) GC plan, multiple deliveries that all reference the same imported object ID will give userspace the same Presence
If a message arrives that re-introduces a vref while it is in the UNREACHABLE state, userspace will get the same Userspace should not be able to tell the difference, because by definition it has dropped all strong references to the original Presence in all four states, so it has nothing to compare the maybe-new Presence against. But, if we add auxdata to the Presences, then userspace can hold onto one of the auxdata properties, without causing the Presence to stick around. Then, when a message arrives, they do an That gives userspace the ability to distinguish a historical transition from UNREACHABLE to COLLECTED, and that transition depends upon the engine's GC schedule. We don't want userspace to be able to sense this, even if we're forcing GC at the end of each crank, because GC might also happen in the middle of a crank, and not very predictably. I don't have a good solution for this yet. The ideas that come to mind:
Boy it'd be nice if JS had a way to create a Selfless data object, or to allow objects to refuse to be compared by identity somehow. @erights do you have any clever ideas here? |
...
Seems pretty reasonable. I gather defensive copies are pretty common with pervasive mutability and iffy equality semantics (e.g. Java beans stuff)
yup. |
The kernelKeeper `addKernelObject` method was updated to accept an `id=` override, to simplify some upcoming unit tests. `deleteKernelObject` was added, which isn't called yet but the upcoming GC changes will invoke it when GC allows a kernel object to be deleted. It's also a placeholder for #2069 auxdata to be deleted.
The kernelKeeper `addKernelObject` method was updated to accept an `id=` override, to simplify some upcoming unit tests. `deleteKernelObject` was added, which isn't called yet but the upcoming GC changes will invoke it when GC allows a kernel object to be deleted. It's also a placeholder for #2069 auxdata to be deleted.
The kernelKeeper `addKernelObject` method was updated to accept an `id=` override, to simplify some upcoming unit tests. `deleteKernelObject` was added, which isn't called yet but the upcoming GC changes will invoke it when GC allows a kernel object to be deleted. It's also a placeholder for #2069 auxdata to be deleted.
I do. When I can take the time to do it, I need to write it up. So I'm adding myself to the assignees for this issue. |
See #6355 |
When krefOf() is called as part of kmarshal.serialize, marshal will only give it things that are 'remotable' (Promises and the Far objects created by kslot()). When krefOf() is called by kernel code (as part of extractSingleSlot() or the vat-comms equivalent), it ought to throw if 'obj' is not one of the Far objects created by our kslot(). This also changes extractSingleSlot() to be just as precise as the old implementation, to be safe against future changes to krefOf() or the marshalling format (e.g. #2069 auxdata adding additional properties).
When krefOf() is called as part of kmarshal.serialize, marshal will only give it things that are 'remotable' (Promises and the Far objects created by kslot()). When krefOf() is called by kernel code (as part of extractSingleSlot() or the vat-comms equivalent), it ought to throw if 'obj' is not one of the Far objects created by our kslot(). This also changes extractSingleSlot() to be just as precise as the old implementation, to be safe against future changes to krefOf() or the marshalling format (e.g. #2069 auxdata adding additional properties).
When krefOf() is called as part of kmarshal.serialize, marshal will only give it things that are 'remotable' (Promises and the Far objects created by kslot()). When krefOf() is called by kernel code (as part of extractSingleSlot() or the vat-comms equivalent), it ought to throw if 'obj' is not one of the Far objects created by our kslot(). This also changes extractSingleSlot() to be just as precise as the old implementation, to be safe against future changes to krefOf() or the marshalling format (e.g. #2069 auxdata adding additional properties).
When krefOf() is called as part of kmarshal.serialize, marshal will only give it things that are 'remotable' (Promises and the Far objects created by kslot()). When krefOf() is called by kernel code (as part of extractSingleSlot() or the vat-comms equivalent), it ought to throw if 'obj' is not one of the Far objects created by our kslot(). This also changes extractSingleSlot() to be just as precise as the old implementation, to be safe against future changes to krefOf() or the marshalling format (e.g. #2069 auxdata adding additional properties).
@erights and I talked through this some more. I think there are a couple of fundamental choices that a system has to make. Object Identifiers: Absolute, or Relative?When vats hear about an object identifier, do all vats get the same string, or are they different for each vat? SwingSet uses relative IDs: the Absolute identifiers would present the same string to all clients. This either requires hierarchical counter-like IDs ( Counter-based object IDs reveal information about how many objects have been allocated, and to prevent this (while still preserving entropy-based uniqueness), the allocator would need to use something like Machine IDs: Stable, or Evolving?In our standard Granovetter diagram, the initial object references are from A->B and from A->C. At the comms level, what that really means is that both B and C have a recognition predicate for A: when a message appears from the internet, claiming to be from A, e.g. C can apply this predicate and decide that the message contents are worthy of accessing resources previously granted to A (e.g. the contents of an A->C c-list). When A does That means A must be able to name B in a message to C, and C must wind up with a recognition predicate for B. In a "public-key style" system (e.g. Foolscap, E's VatTP, IPFS's P2P protocol, I think Tendermint/CometBFT's P2P protocol), each node is identified by a (single) public key: either a public verifying key (where plaintext messages are signed by the corresponding private signing key), or the public half of a DH keypair (which enables encryption as well as authentication). This public key string is then suitable for use as an identifier, and C can build a recognition predicate from it directly (in practice, there's usually a sequence number on each message, so the predicate is somewhat stateful, but the seqnum is unique to each pair of correspondents, so the B->A seqnum is unrelated to the C->A seqnum, and A doesn't need to include it when telling C about B). That means the recognition predicate is "stable": it's the same for each recipient, it does not change over time, and it can be used as a fixed identifier for the machine. Note that the machine ID doesn't have to be exactly the public key: it could be an offline key which is only used to delegate authority to a series of online keys (e.g. "certificate signing key"), or a hash of the key material (where the actual key is fetched at runtime and compared against the hash), or the hash of a stateless program which is retrieved and executed each time a message must be verified. What matters is that it doesn't change over time. In contrast, a proof-of-stake chain uses a recognition predicate that must know the current validator set, which changes over time (the "light client state" is basically a copy of a recent block, which includes the full list of validator keys and their respective voting power). Worse yet, the economics of slashing and unbonding periods mean that the disincentive against equivocation fades over time: if you don't see messages frequently enough (or if you aren't in a position to report evidence of equivocation fast enough to get the miscreant node slashed), you can lose the ability to safely recognize messages from that sender, even though you used to have that ability. A can tell C how it (A itself) (currently) recognizes messages from B, and hopefully that will remain valid for long enough to be useful. But when A tells C about B again next month, the predicate will be different. That means there is no stable/hashable string which can be used to reliably identify B over time. There is generally a "socially unique" string, the Which means that a malicious A might lie to C about B: present the socially-accepted chainID, but with a bogus light client state, with an intention to disrupt C's ability to hear from B in the future. In the immediate sense, this is entirely within A's rights: until C hears about B from someone else, C cannot distinguish the "real" B from some figment of A's imagination. But, if we imagine that C has a table of remotes, and the key column has B's chainID (as reported by A), and the value column has the current light client state (as initialized by A), and later we receive an introduction from D that also claims to talk about B (same chainID, newer recognition predicate), then A's malice might prevent us from ever recording a "good" value for the predicate. (it always comes down to the Grant Matcher Problem) The Cosmos IBC protocol deals with this by requiring special approval (staker vote) for the creation of a channel, which establishes the initial mapping from chainID to light client state. If the predicate ever becomes stale (we fall out of the unbonding window without state updates), the channel freezes until another staker vote delivers a fresh state/block. But normally we get new messages on a regular basis (i.e. at least once a week), which updates the predicate, hopefully keeping it from getting stale. Note that even for a traditional pubkey system, something as common as a key rotation policy (once a week, key N signs key N+1 and stops using key N-1) means we lose the stable machine ID, and have to fall back to the TOFU/specially-verified relationship between machine ID and recognition predicate. Message Ordering / Delays, Sync-vs-Async AuxdataThe final set of properties involve how promptly the auxdata will be available to the message recipient. Ideally, when Alice sends
However, if machine B needs to fetch the auxdata from C, instead of relying upon information supplied by A, then we need one of:
The first option causes message ordering problems (perhaps solvable by retreating from E-Order to FIFO-order, but might allow C to interfere with the ordering of unrelated A->B messages). The second changes the signature of Can auxdata be verified?Now, given those properties of a comms system, under what circumstances can auxdata be verified? The auxilliary data can either come directly from the owner of the object in question, or it can come from an introducer. The only way to make it synchronously available is to come from the introducer. If it comes from the introducer, we want to verify it. The only way to independently verify the auxdata (and ensure that it is immutable / constant-over-time, and that everyone sees the same data) is to hash it and embed the hash into the object identifier. For auxdata that contains object references, the only way to get a constant hash is for the object identifiers to be constant. That requires absolute identifiers (not relative: if B hashes Chains don't have stable recognition predicates, so the best we can do is a socially-unique name and a manual step to establish the mapping, So we would basically need to:
|
Limiting auxdata to data objects that don't contain object references (aka "only bits") seems like it would avoid many of the complications raised above while still providing actual value for a number of use cases. |
This seems predicated on the assumption that whoever introduced Carol to Alice did not include the auxdata along the presence. Why can't we say that in a point to point exchange between 2 parties, if one is introducing a new presence, that introduction includes the auxdata. In the exchange above, Alice would include the auxdata of Carol when introducing Carol to Bob. That way Bob does not need to contact vat C to get the data.
I have to admit I don't fully (yet) grok recognition predicates in the context of blockchains, or even how multiple parties handle authentication in non blockchain contexts. My understanding is that it's roughly built around wrapped signatures / recognition predicates, with possible shortening of auth chains when a direct connection is established between parties. That said I guess I don't really understand what is the fundamental difference between a single presence as we have today and potentially other presences in auxdata. If the receiver of a presence is able to validate/trust the primary presence, why can't it similarly validate/trust presences in auxdata? More specifically, if the auxdata is CapData encoded, it would be composed of a stable body (which can be hashed for verification), and slots which can be rewritten when going through layers the same way the primary presence would be. At the end of the day, I consider our current presence sharing as a degenerate case of a more generic presence sharing mechanism which represents each "presence" as more complex passable data. In that model, what we currently have is the equivalent of a "body" solely composed of a single slot, and the primary presence in that slot. |
to aid in finding this via search: auxiliary has 1 l, not 2. |
the "TODO: decref #2069 auxdata" comment was removed, because that will be the responsibility of deleteKernelObject()
the "TODO: decref #2069 auxdata" comment was removed, because that will be the responsibility of deleteKernelObject()
the "TODO: decref #2069 auxdata" comment was removed, because that will be the responsibility of deleteKernelObject()
the "TODO: decref #2069 auxdata" comment was removed, because that will be the responsibility of deleteKernelObject()
the "TODO: decref #2069 auxdata" comment was removed, because that will be the responsibility of deleteKernelObject()
the "TODO: decref #2069 auxdata" comment was removed, because that will be the responsibility of deleteKernelObject()
What is the Problem Being Solved?
Pass-by-presence objects are currently (almost) pure behavior. If you hold a Presence, the (almost) only thing you can do with it is:
The one partial exception is that we associate an "Interface name" (a sort of alleged type) with the Presence, which can be retrieved with a helper function (#1816).
On the other hand, if you send a pass-by-copy object (data but no behavior), it behaves like a normal javascript object, except you can't send it messages, and you can't compare it for identity (each act of transmission results in a new copy of the object.. if we could, we'd probably want to throw an error if you ever tried to use
===
on it, or use it as the key of a Map/WeakMap).I've wondered if it would make development easier if we added immutable data to pass-by-presence
copyobjects. This would subsume the currentgetInterfaceOf(presence)
call (you'd just dopresence.interface
instead). The vat which creates the Remotable could add whatever non-Function properties it likes, as long as the object isharden()
ed, and the non-Function properties would be serialized as usual and attached to all copies of the Presence.This might allow (alleged) Brands to be attached to Purses/Payments, instead of requiring a
getBrand()
roundtrip. We could add descriptive names to objects, for debugging.This relates to:
Remotable
and Function-bearing objects (with or without non-Function data) would be pass-by-presenceDescription of the Design
My notion is:
syscall.getDataForObject
to retrieve the auxililary data the first time they receive an object, and the kernel can dodispatch.getDataForObject
to fetch it from a vat shortly after the vat exports it for the first timeExtra feature: the auxilliary data could include additional references (to other objects and/or promises). These would represent new edges in the kernel-side object graph, which influences GC. I think this would be particularly useful for a Brand.
Security Considerations
This would be an additional way for vats to introduce reference cycles into the kernel data structures (they can already do so with mutually-referential promise resolutions), which may be hard to clean up: cycles require a full mark-and-sweep GC pass, rather than merely watching for refcounts to drop to zero.
This would make it harder to implement mostly-transparent proxy wrappers, such as a recovable forwarder, since the wrapper would need to enumerate all the auxilliary data and copy it (in wrapped form) onto the new proxy. A common bug would be to forget to copy the data properties, leading to a wrapper that behaves like the original but looks bare.
Open Questions
Does this seem useful? @erights @katelynsills @Chris-Hibbert are there ERTP things that would be easier or more ergonomic to express if you could do property lookups on your Presence objects?
For me, the high-order question is whether this auxilliary data can contain object/promise references, or if it is restricted to purely pass-by-copy data. If so, the kernel object table will be a source of GC references. I don't think it's significantly more work for me (in #1872) to accomodate this, since the kernel promise table is already a source, but I do wonder how much I should plan to accomodate it.
Does the "ask if you don't already have it" protocol feel right? We could explore it more, but my hunch is that having both sides know for sure whether the other side already knows about the data would complicate the GC protocol.
The text was updated successfully, but these errors were encountered: