Skip to content
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

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

dckc
Copy link
Member

@dckc dckc commented Dec 7, 2022

refs: #6625, #6038

based on feedback, plan is to add support for this in endo, starting with

Description

In #6625, @warner writes:

I think we're seeing the same "long time to serialize" problem ...

That reminded me of the two round trips in each wallet state update - one for serialize and one for setValue:

image

by changing E(marshaller).serialize(value).then(x => E(storageNode).setValue(JSON.stringify(x)) to E(storageNode).setValue(E(marshaller).serializeAndStringify(x)) we can save a round trip for each:

image

gist with before and after .svg diagrams

Drawbacks

  • we were using serialize from the marshal API. serializeAndStringify means a distinct interface/type threaded through all the places that use storage nodes.
    • It's a little awkward, but saving a round trip is worth it, yes?

Security Considerations

  • setValue() takes a promise now. Using pattern guards in makeChainStorageRoot would let us use callAfter as well as the usual security benefits

Documentation Considerations

?

Testing Considerations

  • serializeAndStringify unit test in test-lib-board.js

@dckc
Copy link
Member Author

dckc commented Dec 7, 2022

@warner @michaelfig @gibson042 @turadg

I'd like to get some feedback on this in draft form.

Copy link
Member

@gibson042 gibson042 left a 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.

Comment on lines 70 to 71
withStringify({
...makeMarshal(val => {
Copy link
Member

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?

Copy link
Member

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).

Copy link
Member Author

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.)

Copy link
Member

@erights erights Jan 27, 2023

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.

@ivanlei ivanlei requested a review from erights January 23, 2023 18:34
@dckc
Copy link
Member Author

dckc commented Jan 23, 2023

@erights @warner this is the one that is pending endojs/endo#1248

@turadg
Copy link
Member

turadg commented Jan 23, 2023

pending endojs/endo#1248

I expect we can resolve naming considerations (including 1248) after landing this as a feature.

@erights
Copy link
Member

erights commented Jan 23, 2023

Where is the definition of serializeAndStringify? I couldn't find it.

@dckc
Copy link
Member Author

dckc commented Jan 23, 2023

Where is the definition of serializeAndStringify? I couldn't find it.

https://github.com/Agoric/agoric-sdk/pull/6646/files#diff-298ac87f534f4fb60e42f478a8a866d0eb2f4e4853f6c7cb12c7f99c92594b2eR64

p.s. that didn't work as expected. But it's one line:

    serializeAndStringify: v => JSON.stringify(m.serialize(v)),
  });

@dckc dckc added this to the Vaults EVP milestone Feb 9, 2023
@dckc dckc mentioned this pull request Feb 15, 2023
@dckc dckc self-assigned this Feb 15, 2023
@dckc dckc changed the title serializeAndStringify to save a round-trip (WIP) serializeAndStringify to save a round-trip Apr 14, 2023
@dckc dckc marked this pull request as ready for review April 14, 2023 17:08
@dckc
Copy link
Member Author

dckc commented Apr 14, 2023

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 toCapData landed in endo (endojs/endo#1402) but it seems to be part of the pile of things waiting for endo/agoric-sdk sync (#7090).

I see

I expect we can resolve naming considerations (including 1248) after landing this as a feature.

Is that still the case? We can land this as serialzeAndStringify?

@mhofman
Copy link
Member

mhofman commented Apr 14, 2023

Is that still the case? We can land this as serialzeAndStringify?

I still believe the name is not right. CapData is an encoding, not a serialization. I would rather encodeAndStringify or toCapDataAndStringify.

Edit: to clarify, the output of toCapData is something that cannot be stored or transmitted as-is by our current abstractions, and thus does not qualify the definition of serialization. JSON.stringify is however a serialization.

In computing, serialization (or serialisation) is the process of translating a data structure or object state into a format that can be stored (e.g. files in secondary storage devices, data buffers in primary storage devices) or transmitted (e.g. data streams over computer networks) and reconstructed later (possibly in a different computer environment).[1] When the resulting series of bits is reread according to the serialization format, it can be used to create a semantically identical clone of the original object.

@dckc dckc changed the title serializeAndStringify to save a round-trip feat: integrate JSON.stringify/parse in board marshallers Apr 14, 2023
@dckc
Copy link
Member Author

dckc commented Apr 14, 2023

@mhofman suggested integrating JSON.parse as well as JSON.stringify for similar reasons.

parseAndDecode(str) {
const data = JSON.parse(str);
const readonly = makeReadonlyMarshaller(this.state);
return readonly.unserialize(data);
Copy link
Member

@turadg turadg Apr 14, 2023

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.

Copy link
Member Author

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

Copy link
Member

@gibson042 gibson042 left a 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.

Comment on lines 233 to 234
// @ts-expect-error M.callWhen
return E(storageNode).setValue(serializedP);
Copy link
Member

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 awaiting its argument?

Suggested change
// @ts-expect-error M.callWhen
return E(storageNode).setValue(serializedP);
return E(storageNode).setValue(serializedP);

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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(),

Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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?

Suggested change
// @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.

Copy link
Member Author

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.

Copy link
Member

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')),
 });

@@ -326,7 +327,6 @@ export const prepareBoardKit = baggage => {
/**
* @param {string} id
* @throws if id is not in the mapping
* @returns {Marshaller}
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @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
 */
Suggested change
* @returns {Marshaller}
* @returns {ExtendedMarshaller}

Copy link
Member Author

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.

Copy link
Member

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.

@dckc dckc marked this pull request as draft May 6, 2023 23:19
@dckc dckc removed this from the Vaults EVP milestone May 8, 2023
@dckc
Copy link
Member Author

dckc commented May 8, 2023

Cleared the milestone at @turadg 's suggestion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants