-
Notifications
You must be signed in to change notification settings - Fork 215
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
feat: integrate JSON.stringify/parse in board marshallers #6646
base: master
Are you sure you want to change the base?
Conversation
@warner @michaelfig @gibson042 @turadg I'd like to get some feedback on this in draft form. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the approach, but I hate the naming (although that's nothing new; this is just further fallout from failing to address endojs/endo#1248 ). But yes, I agree that saving the unnecessary round trips seems worthwhile.
packages/vats/src/lib-board.js
Outdated
withStringify({ | ||
...makeMarshal(val => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've we're going to adopt this, why not have makeMarsal
(if not an even deeper layer) provide the composite method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to get some feedback on this in draft form.
I echo @gibson042's desire for clearer names and to consider putting this into Endo instead.
IMO it's not too late to advance endojs/endo#1248 . The new method serializeAndStringify
could be more clear as toCapDataAndStringify
or stringifyAsCapData
. I'd suggest serializeAsCapData
but that term is ambiguous now (ergo issue 1248).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, cool... so pushing it down into endo is pretty clearly The Right Thing. (I wasn't aware of 1248 and I didn't know how receptive others would be to the suggestion.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it is still not too late to rename the marshal methods. I approved endojs/endo#1402 last year and my LGTM still stands. There is no unresolved "Request changes".
I know from painful experience that renaming without breaking things can be painful but worth it. endojs/endo#1402 is the right first step -- it just introduces the new names while deprecating the old.
I am open to having makeMarshal
provide the composite methods. This makes more sense to me, but I'll of course withhold judgement until I see what it looks like. But please do endojs/endo#1402 first, unless there's an impediment I'm not seeing.
@erights @warner this is the one that is pending endojs/endo#1248 |
I expect we can resolve naming considerations (including 1248) after landing this as a feature. |
Where is the definition of |
p.s. that didn't work as expected. But it's one line: serializeAndStringify: v => JSON.stringify(m.serialize(v)),
}); |
What next? I updated this branch. Since the board became upgradeable, the rebase was so messy I just re-did the changes. Now where are we on naming? I see I see
Is that still the case? We can land this as |
I still believe the name is not right. CapData is an encoding, not a serialization. I would rather Edit: to clarify, the output of
|
@mhofman suggested integrating |
parseAndDecode(str) { | ||
const data = JSON.parse(str); | ||
const readonly = makeReadonlyMarshaller(this.state); | ||
return readonly.unserialize(data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be fromCapData
. the Endo sync is imminent and will save some churn to wait for it: https://github.com/Agoric/agoric-sdk/pull/7273/files#diff-298ac87f534f4fb60e42f478a8a866d0eb2f4e4853f6c7cb12c7f99c92594b2eR22
though I suppose this could land first and that PR could update the call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will save some churn to wait for it
ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with #6646 (comment) regarding naming.
// @ts-expect-error M.callWhen | ||
return E(storageNode).setValue(serializedP); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this include generalization of StorageNode setValue
to include await
ing its argument?
// @ts-expect-error M.callWhen | |
return E(storageNode).setValue(serializedP); | |
return E(storageNode).setValue(serializedP); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC, M.callWhen
ensures the promise is resolved before calling the setValue
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, but I don't see the expected update to introduce that M.callWhen
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update? isn't it already there?
setValue: M.callWhen(M.string()).returns(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, then what's the TS error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TS error is because the caller passes a Promise<string>
and the callee is declared to accept string
. tsc
isn't aware of M.callWhen
fixing things up in the middle. Hence @tsc-expect-error M.callWhen
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it wouldn't make more sense to deal with that at the source in lib-chainStorage.js by fibbing like /** @type {(value: ERef<string>) => Promise<void>} */
, but if not then maybe provide more context here?
// @ts-expect-error M.callWhen | |
return E(storageNode).setValue(serializedP); | |
// @ts-expect-error M.callWhen auto-awaits serializedP for setValue(value: string) | |
return E(storageNode).setValue(serializedP); |
But I get the shorthand now so these are completely non-blocking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fibbing means the setValue
implementation won't be typechecked correctly. But I suppose it would make sense to put 1 @ts-expect-error
or the like there rather than 1 for every caller.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewing @endo/patterns, it also looks like ChainStorageNodeI
isn't quite right... I think it needs the following change:
const ChainStorageNodeI = M.interface('StorageNode', {
- setValue: M.callWhen(M.string()).returns(),
+ setValue: M.callWhen(M.await(M.string())).returns(),
getPath: M.call().returns(M.string()),
getStoreKey: M.callWhen().returns(M.record()),
makeChildNode: M.call(M.string())
.optional(M.splitRecord({}, { sequence: M.boolean() }, {}))
.returns(M.remotable('StorageNode')),
});
packages/vats/src/lib-board.js
Outdated
@@ -326,7 +327,6 @@ export const prepareBoardKit = baggage => { | |||
/** | |||
* @param {string} id | |||
* @throws if id is not in the mapping | |||
* @returns {Marshaller} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this being removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because Marshaller
doesn't have the 2 new methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* @returns {Marshaller} | |
* @returns {Marshaller & { | |
serializeAndStringify: (value: unknown) => string, | |
parseAndDecode: (value: string) => any, | |
}} |
or better yet,
// TODO Move into endo.
/**
* @typedef {Marshaller & {
* serializeAndStringify: (value: unknown) => string,
* parseAndDecode: (value: string) => any,
* }} ExtendedMarshaller
*/
* @returns {Marshaller} | |
* @returns {ExtendedMarshaller} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We typically leave return types inferred, no?
But yes, this type is needed in lots of other places, so I have started on an ExtendedMarshaller
type. Might as well use it here, I suppose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We typically leave return types inferred, no?
I don't know about typically, but my own practice is that when typing a function at all, I usually state the return type I expect explicitly, in order to catch surprises earlier.
Cleared the milestone at @turadg 's suggestion. |
refs: #6625, #6038
based on feedback, plan is to add support for this in endo, starting with
Description
In #6625, @warner writes:
That reminded me of the two round trips in each wallet state update - one for
serialize
and one forsetValue
:by changing
E(marshaller).serialize(value).then(x => E(storageNode).setValue(JSON.stringify(x))
toE(storageNode).setValue(E(marshaller).serializeAndStringify(x))
we can save a round trip for each:gist with before and after .svg diagrams
Drawbacks
serialize
from the marshal API.serializeAndStringify
means a distinct interface/type threaded through all the places that use storage nodes.Security Considerations
setValue()
takes a promise now. Using pattern guards inmakeChainStorageRoot
would let us usecallAfter
as well as the usual security benefitsDocumentation Considerations
?
Testing Considerations
serializeAndStringify
unit test intest-lib-board.js