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

10130 excise SharedStateRecord #10140

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open

Conversation

turadg
Copy link
Member

@turadg turadg commented Sep 24, 2024

closes: #10130

Description

Provide examples of how a contract can have a shared local account, without the race condition of creating it conditionally in a flow.

In the course of this I corrected deposit of LocalOrchestrationAccount['holder'] to match the cosmos-api types (not returning the Amount).

Security Considerations

none, just example code

Scaling Considerations

none, example code

Documentation Considerations

Examples are implicit documentation. Can be mined for docs site tutorials if the need arises.

Testing Considerations

The upgrade test for send-anywhere is disabled, but since this just makes a vow there shouldn't be any upgrade interactions.

Upgrade Considerations

The examples aren't to be deployed.

The remove of sharedStateRecord won't affect anything on chain. Same for changing the return of deposit.

Copy link

cloudflare-workers-and-pages bot commented Sep 24, 2024

Deploying agoric-sdk with  Cloudflare Pages  Cloudflare Pages

Latest commit: 195c1f5
Status:🚫  Build failed.

View logs

@mhofman mhofman self-requested a review September 24, 2024 18:30
Copy link
Member

@mhofman mhofman left a comment

Choose a reason for hiding this comment

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

I am surprised to not see a test failure with this change.

The misbehavior of the guest will cause the guest invocation to become suspended until an upgrade to a new version which repairs the behavior. That said I'd expect the test to fail if the invocation doesn't complete as expected.

Copy link
Member

Choose a reason for hiding this comment

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

There's a little more to excise in the endowments logic, but removing the helper is a good start to prevent benign usage.

Comment on lines 47 to 50
async () => {
const agoricChain = await orch.getChain('agoric');
return agoricChain.makeAccount();
},
Copy link
Member

Choose a reason for hiding this comment

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

Yeah this is not supported. First, a function is not passable. And even if we had a Far function, no guest created capability (aka remotable or promise, not copy data) can currently be passed back. The main problem is that when you pass a guest created remotable, there is no guarantee the host will only use that capability before the flow invocation completes. Ideally we'd need a way for the author to make explicit that the capability is no longer expected to be used, aka a revocation mechanism, and it would be an error of the flow to complete without explicitly revoking these ephemeral remotables first. The problem with promises is somewhat similar: we could allow them but enforce that the promise is settled by the flow by the time the flow completes. Without that it would be useless to the host.

Copy link
Member

Choose a reason for hiding this comment

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

Would something like provideAccountForChain(orch.getChain('agoric')) work?

If you want to define this logic collocated, I'm wondering if we'd be able to use the orchestrateAll trick (likely with some changes in endowments needed), aka await atomicProvider.provideAsync('localAccount', flows.makeLocalAccount).

This all seems very clunky though, and I'm not sure how to make this cleaner. Is this a generic pattern that we need to support, or something we could have a helper built into orch (which would raise the question of how the orch state is shared between multiple orchestrated flows)?

Copy link
Member

@mhofman mhofman left a comment

Choose a reason for hiding this comment

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

There seem to be some kind of design tension here in the orch API. From what I understand:

  • Some contracts need a "local account" that is shared between multiple invocations of orchestrated flows (possibly all of them?)
  • Creating that singleton local account is dependent on network and itself needs to be resumable

If this is something the orch API cannot facilitate better, then an orchestrated provider pattern is the correct idea, but the layering right now is not quite right. There is however simply no way around the flow having to await the provided orch account.

Comment on lines 40 to 52
const sharedLocalAccount = await atomicProvider.provideAsync(
'localAccount',
key => vowTools.when(orchestrate(key, {}, sharedFlows.makeLocalAccount)()),
);
Copy link
Member

Choose a reason for hiding this comment

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

If I understand what's going on here, this likely does not work on restart, and is upgrade hazardous for other reasons.

Afaik, provideAsync lazily invokes the maker if the value is not found in the store (nor pending).

orchestrate is an exo definition that needs to be rewired during start for every subsequent incarnation.

Given that the maker given to provide here contains the orchestrate call, and that it only happens once, the orchestrated flow will never be rewired in future incarnations, preventing the vat from upgrading.

Furthermore, the await here is for a non immediate promise. It is either dependent on network or on other vats, and would prevent the start result promise from immediately settling during the first crank. The only reason it works right now is because the first incarnation of the contract has this logic, and there is a limitation in zcf which cannot enforce single crank first start of contract (because the first zcf start is actually done in a second crank). If this was added as new logic in a second incarnation of the contract, the upgrade to that incarnation would fail.

I believe the following would work:

Suggested change
const sharedLocalAccount = await atomicProvider.provideAsync(
'localAccount',
key => vowTools.when(orchestrate(key, {}, sharedFlows.makeLocalAccount)()),
);
const { makeLocalAccount } = orchestrateAll(sharedFlows, {});
const sharedLocalAccount = zone.makeOnce(
'localAccount',
key => makeLocalAccount(),
);

Then of course you still need to await the sharedLocalAccount inside flow, but there is just no way around that.

I imagine a slightly more reusable approach would be:

  const orchAccountsProvider = zone.subZone('orchAccounts');
  const { makeOrchAccount } = orchestrateAll(sharedFlows, {});
  const provideOrchAccount = chain => orchAccountProvider.makeOnce(chain, makeOrchAccount);

Then provideOrchAccount can be given as an endowment. It's return value of course must still be awaited in flows.

Copy link
Member Author

Choose a reason for hiding this comment

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

Great review! 🙏

@turadg turadg force-pushed the 10130-excise-SharedStateRecord branch from f5198ed to 195c1f5 Compare October 1, 2024 23:33
@turadg turadg marked this pull request as ready for review October 1, 2024 23:33
@turadg turadg requested a review from a team as a code owner October 1, 2024 23:33
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.

helper for async providing a value into a SharedStateRecord
2 participants