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

fix: far classes with interface guards, used by ERTP #5960

Merged
merged 2 commits into from
Sep 3, 2022

Conversation

erights
Copy link
Member

@erights erights commented Aug 14, 2022

Prior to this PR, virtual and durable far objects could be written using the various "kind" definers

  • virtual vs durable (define*Kind vs define*DurableKind)
  • single object vs cohort of facets (define*Kind vs define*KindMulti)
  • whether durables are automatically registered with baggage (defineDurableKind* vs vivifyKind*)
  • there is also a special case for durable singletons (vivifySingleton)

In practice we expect only three to be common (See #5708 )

  • vivifyDurableKind
  • vivifyDurableKindMulti
  • vivifySingleton

This PR introduces replacements for these, in order to address two problems:

  • Kind definers require their methods to take an additional context first argument for receiving the object state and access to self or cohort of facets. This shifts the argument types by one, which creates a painful IDE experience (See fix: far classes with interface guards, used by ERTP #5960 (comment) below. (Note that singletons do not have this problem.)
  • As far objects, these may be exposed to malicious messages, both intra-vat (eventually) and inter-vat. Thus they must be defensively consistent, which requires strong input validation. Experience shows that it is tricky to write adequate input validation manually and to review for adequacy. (This applies to normal non-virtual far objects as well, which will be addressed by fix: heap far classes #6107 .)

The replacements

  • virtual vs durable (defineVirtualFarClass vs defineDurableFarClass)
  • single object vs cohort of facets (define*FarClass vs define*FarClassKit)
  • are durables auto registered in baggage (defineDurableFarClass* vs vivifyFarClass*)
  • special case for singletons (vivifyFarInstance)

Its worth noting that we expect conversion from Kind style to Far Class style not to effect which are common in practice:

  • vivifyFarClass
  • vivifyFarClassKit
  • vivifyFarInstance

It addresses the two problems mentioned above

  • The methods provided to the FarClass definers take their context as their this argument, leaving their function type unaffected. This requires that these methods be written as functions that receive their this binding dynamically. We chose to use concise method syntax, where all these methods are written inline within the call to the FarClass definer. Note that these constraints are familiar to JS class programmers, whose methods must also be written in concise method syntax that must appear inline within the class definition.
  • This PR extends our pattern sublanguage with M.interface and helpers M.call, M.callWhen, and M.await for defining protective interfaceGuards that can express and enforce type-like constraints on incoming arguments and return results. This sublanguage needs to be separately documented. The FarClass definer functions all take an interfaceGuard argument. The methods exposed as the API will check all their incoming arguments before calling the raw methods provided in the same call to the FarClass definer.

The raw methods can thus assume that all the validation expressed by the interfaceGuard for this method has already been checked. It is still the responsibility of the raw method to validate all issues that remain after the interfaceGuard has done its part of the job. For example, for a payment argument, the interfaceGuard can only validate that it is a remotable. It remains to the raw method to manually check, for example, that it is a live payment of the correct brand containing enough assets.

This raises the other reason we need to write these methods inline: By themselves, they are fragile. Also, by themselves, they are this-sensitive functions. For both of those reasons, we'd like to avoid the possibility of them floating around as first-class values. By having them appear inline in the FarClass definer call, we successfully encapsulate them. Afterward, they can only be reached by the protective outer shell described by the interfaceGuard.

After this PR is merged and we have experience using it, we hope to decide to deprecate the Kind functions and style in favor of the Far Class style. But since this decision should follow experience, we leave it to the follow on PR #6106 .

@erights erights self-assigned this Aug 14, 2022
@erights erights mentioned this pull request Aug 14, 2022
@erights erights force-pushed the markm-interfaces branch 2 times, most recently from 072cfff to 49d520a Compare August 15, 2022 02:04
@erights erights force-pushed the markm-far-classes branch from b65762a to bf37be3 Compare August 15, 2022 02:05
@erights erights force-pushed the markm-far-classes branch from d66b625 to bc94cb3 Compare August 15, 2022 07:40
@erights erights force-pushed the markm-far-classes branch from bc94cb3 to e75743d Compare August 16, 2022 23:01
@erights erights changed the base branch from markm-interfaces to markm-representatives-inherit August 16, 2022 23:04
@erights erights marked this pull request as ready for review August 16, 2022 23:05
@erights erights requested a review from turadg as a code owner August 16, 2022 23:05
@erights
Copy link
Member Author

erights commented Aug 16, 2022

Reviewers,

Not really ready for review. Please ignore for now and pay attention onto to #5970
I did this only to tickle CI, which didn't work anyway.

6 skipped and 1 successful checks

If someone understands what I need to do to tickle CI into running all our normal tests, please let me know.

@erights erights marked this pull request as draft August 16, 2022 23:17
@erights erights force-pushed the markm-far-classes branch 2 times, most recently from 48d13da to 874c1dd Compare August 17, 2022 07:32
@mhofman mhofman added the force:integration Force integration tests to run on PR label Aug 17, 2022
@mhofman
Copy link
Member

mhofman commented Aug 17, 2022

If someone understands what I need to do to tickle CI into running all our normal tests, please let me know.

Draft PR, or for that matter PRs that are not ready for merge don't run integration tests. Those can be forced with the force:integration label that I've just applied to this PR. I doubt however that the integration tests would catch something the unit tests didn't in this case.

@mhofman
Copy link
Member

mhofman commented Aug 17, 2022

Deployment test failed because of #5970 (comment). Not sure why getting-started failed

@erights erights force-pushed the markm-representatives-inherit branch from d0534b0 to 48f3063 Compare August 17, 2022 16:54
@erights erights force-pushed the markm-far-classes branch from 874c1dd to 6593737 Compare August 17, 2022 16:55
@erights
Copy link
Member Author

erights commented Aug 17, 2022

Still more typing work to be done, but recording this for now. This PR introduces a "thisful" style as an alternative, where the context is passed as this rather than as first argument. This has costs and benefits, as demonstrated by the contrast between the first pair of images (from #5970) vs the second pair, from the current state of this PR.

Advantage thisful style:
In both, the types are actually correct (and with more work would be more precise). But the hover on the first shows the unshifted types inferred from the behavior methods, while also showing the KindFacets<...> around it to transform it into the correct shifted types of the callable methods. To understand the type shown in the first hover, you need to simulate the KindFacets<...> transform in your head. This isn't hard. But the verbosity of the unshifted types makes these hovers much less useful. The second hover is both easier to understand and much less verbose, and so more informative.

Advantage context-argument style, arguably:
With the first context-argument style, the context is the first normal argument and the behavior methods are normal Jessie code written as arrow function. With the second thisful style, the behavior methods receive their context as their this, and so must be written using a syntax for functions that can dynamically receive their this binding from their callers. The natural one to use is the concise method syntax. Using classes would force this choice -- class methods can only be expressed using concise method syntax.

image

...

image

vs
image
...
image

@mhofman
Copy link
Member

mhofman commented Aug 17, 2022

One thing I noticed is that this doesn't actually refer to self for single faceted objects (or the current facet for multi-facet objects), but the context first arg we currently have. While that make sense, it might end up somewhat surprising for developers, especially if we do end up with class syntax.

One mitigation might be to cheat and have the this arg be facet specific, and have as prototype the "self" facet. Aka, const thisArg = harden(Object.create(selfFacet, getOwnPropertyDescriptors(context)));. However we do paint ourselves in a corner with future extensibility of the context object, risking conflict with existing methods.

If we considered only developer experience, really the state and facets would be something like private fields (this.#state and this.#facets), while leaving identically named self behavior methods accessible without conflicts (e.g. this.state()). However I don't think there is a way to accomplish this without something like decorators (so we can initialize the field and prevent re-assignments):

class Counter {
  @state #state;
  @facets #facets;

  inc(n = 1) {
    return this.#state.value += n;
  }
}

A cheap alternative might be to use symbols, but that may require making the behavior declaration a "maker":

defineKind('counter', () => ({ value: 0 }), ({ stateSym, facetsSym }) => ({
  inc(n = 1) {
    return this[stateSym].value += n;
  },
}));

@erights erights force-pushed the markm-representatives-inherit branch from 48f3063 to 0493dc1 Compare August 17, 2022 18:14
@erights erights force-pushed the markm-far-classes branch from 6593737 to 2185df0 Compare August 17, 2022 18:14
@erights
Copy link
Member Author

erights commented Sep 1, 2022

@Chris-Hibbert @gibson042
I need to do some writing in order to address the remaining review comments. But I think I've done everything else, so if you're ok starting to PTAL while I gear up to write, PTAL. Thanks!

Copy link
Contributor

@Chris-Hibbert Chris-Hibbert left a comment

Choose a reason for hiding this comment

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

I'm fairly comfortable with the consequences in the ERTP code. The implementation is beyond me to verify.

I doubt I can make use of the tools in any novel cases without help. There are quite a few examples in the ERTP code to copy from, but I'll bet governance (for example) does things that ERTP doesn't, and I don't see documentation on the semantics or usage of the M.whatever() functions.

I can intuit the meaning of

    M.callWhen(M.await(PaymentShape))
      .optional(M.pattern())
      .returns(amountShape),

from context, but I'm not confident that that will be enough for me to use the tools without a fair number of questions.

@erights
Copy link
Member Author

erights commented Sep 1, 2022

Reviewers, do you agree that the following can wait until after this PR?

TODO: Extract some of the text above to serve as documentation of Far Classes without reference to kinds. This can serve as an explanation to programmers who started with Far classes and never need to learn about kinds.

TODO: Provide FarClass definers of normal in-memory heap objects #6107

TODO: Deprecate kinds #6106

TODO: Describe the pattern sublanguage, including the new interface guards.

TODO: Explore flatter this structures, so we can avoid the annoyance of always saying this.state.foo to access our own instance variable. See #5960 (comment)

TODO: conventional class programmers are used to doing composition by inheritance. For the objects-as-closure style currently supported by Far, we've been doing composition with .... We have neither for Far classes. Attn @Chris-Hibbert @dtribble Governance currently makes heavy use of ... composition, so it is urgent to figure out what to do instead. (Though we have an emergency stopgap: bindAllMethods that we can use in the short term.)

@erights
Copy link
Member Author

erights commented Sep 1, 2022

This PR is ready for review.

Please add some detail to the top comment, which says only TODO describe PR.

Done.

@erights erights added the automerge:squash Automatically squash merge label Sep 1, 2022
@Chris-Hibbert
Copy link
Contributor

Reviewers, do you agree that the following can wait until after this PR?

I agree. Some of the doc is a hurdle for me, but that's about follow-on work, not this PR.

@erights erights removed the automerge:squash Automatically squash merge label Sep 1, 2022
@Chris-Hibbert Chris-Hibbert self-requested a review September 1, 2022 22:08
Copy link
Contributor

@Chris-Hibbert Chris-Hibbert left a comment

Choose a reason for hiding this comment

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

Changing to comment to prevent accidental submission based only on this review.

The parts I understand still LGTM.

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.

@erights LGTM! :shipit:

packages/ERTP/src/paymentLedger.js Show resolved Hide resolved
packages/ERTP/src/typeGuards.js Show resolved Hide resolved
@gibson042
Copy link
Member

Also, I agree with the proposed TODOs.

@erights erights added the automerge:squash Automatically squash merge label Sep 3, 2022
@dtribble
Copy link
Member

dtribble commented Sep 3, 2022

TODO: conventional class programmers are used to doing composition by inheritance. For the objects-as-closure style currently supported by Far, we've been doing composition with .... We have neither for Far classes. Attn @Chris-Hibbert @dtribble Governance currently makes heavy use of ... composition, so it is urgent to figure out what to do instead. (Though we have an emergency stopgap: bindAllMethods that we can use in the short term.)

Governance succeeds by using the "shared method pattern" to include governance operations when the class is defined rather than trying to stick them onto the instance at the end. As far as I'm concerned that's better anyway, and is a better match for future inheritance.

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

Successfully merging this pull request may close these issues.

5 participants