-
Notifications
You must be signed in to change notification settings - Fork 214
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(orchestration): ZCFTools #10057
Conversation
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 needs to also update the orchestration facades to replace any full ZCF in a context to the attenuated one. Once that's done some of the existing example tests will cover this.
Deploying agoric-sdk with Cloudflare Pages
|
Yes, a singleton durable exo can be created whose behavior wraps over the singleton zcf object provided to the contract start function. |
Good question. I'm actually wondering if patterns could help here. Effectively the new objects have to implement all previous methods, accepting at least the same arguments that were previously accepted. The return values must have at least the same fields that were previously present. TypeScript types might be able to help here, as this is effectively the definition of assignability. |
packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.md
Outdated
Show resolved
Hide resolved
packages/orchestration/src/facade.js
Outdated
@@ -64,6 +65,8 @@ export const makeOrchestrationFacade = ({ | |||
|
|||
const { prepareEndowment, asyncFlow } = asyncFlowTools; | |||
|
|||
const zcfLtd = prepareZcfForFlows(zcf, zone, vowTools); |
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 think this needs to be called by provideOrchestration
using zones.orchestration
(aka the system orchestration zone), and returned as one of the orchestration tools.
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.
What's the impact on contracts?
I started making this change... it's straightforward enough... but it means that all the contracts don't use the attenuated ZCF unless we change them to opt-in. Is that by design?
How does this impact the 1st arg to the withOrchestration
callback (usually contract
)? Do we keep the normal zcf there plus the attenuated zcf in the tools
?
const contract = async (zcf, _privateArgs, zone, tools) => { ... }
export const start = withOrchestration(contract);
25a1716
to
0e3215c
Compare
0e3215c
to
cd7e632
Compare
|
||
import { M } from '@endo/patterns'; | ||
|
||
export const ZcfI = M.interface( |
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 is different than ZcfI
exported from @agoric/zoe
. Let's have a distinct name.
I'd suggest ZcfForFlowsI
but that's quite clunky. But that goes for the whole Exo name imo.
Would FlowsZcf
, FlowsZcfI
and flowsZcf
suffice? JSdoc could resolve any ambiguity
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.
One advantage of zcfForFlows
is that it starts with zcf
, and makes autocomplete simpler.
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.
sorting with ZCF seems worthwhile. going with ZcfForFlowsI
/** | ||
* @deprecated | ||
* @type {ZCF['reallocate']} | ||
*/ | ||
reallocate(seat1, seat2, ...seatRest) { | ||
return zcf.reallocate(seat1, seat2, ...seatRest); | ||
}, |
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.
no need to propagate this function on the chopping block
/** | |
* @deprecated | |
* @type {ZCF['reallocate']} | |
*/ | |
reallocate(seat1, seat2, ...seatRest) { | |
return zcf.reallocate(seat1, seat2, ...seatRest); | |
}, |
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. it was specified in #9773, but I'm dropping it.
packages/orchestration/src/facade.js
Outdated
@@ -78,6 +78,11 @@ export const makeOrchestrationFacade = ({ | |||
const [wrappedCtx] = prepareEndowment(subZone, 'endowments', [hostCtx]); | |||
const hostFn = asyncFlow(subZone, 'asyncFlow', guestFn); | |||
|
|||
deepMapObject( | |||
wrappedCtx, | |||
val => val === zcf && assert.fail('do not pass zcf'), |
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.
slick
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.
Should the message point towards using zcfForFlows
too?
|
||
test.before('set up context', async t => (t.context = await makeTestContext())); | ||
|
||
test('yes as is: atomicRearrange(), ... getTerms()', async t => { |
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.
took me a bit to understand the test name. consider,
- removed
- unchanged
- changed:
- changed:
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.
Just a small request on the makeInvitation
guard. I hadn't realized why it had this shape in the first place.
|
||
import { M } from '@endo/patterns'; | ||
|
||
export const ZcfI = M.interface( |
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.
One advantage of zcfForFlows
is that it starts with zcf
, and makes autocomplete simpler.
packages/orchestration/src/facade.js
Outdated
@@ -78,6 +78,11 @@ export const makeOrchestrationFacade = ({ | |||
const [wrappedCtx] = prepareEndowment(subZone, 'endowments', [hostCtx]); | |||
const hostFn = asyncFlow(subZone, 'asyncFlow', guestFn); | |||
|
|||
deepMapObject( | |||
wrappedCtx, | |||
val => val === zcf && assert.fail('do not pass zcf'), |
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.
Should the message point towards using zcfForFlows
too?
test('makeInvitation: non-passable handler', async t => { | ||
const { zoe, zcf, zcfLtd, vt } = t.context; | ||
|
||
const toTradeVow = zcfLtd.makeInvitation(_seat => {}, 'trade'); | ||
|
||
const toTrade = await vt.when(toTradeVow); | ||
const amt = await E(E(zoe).getInvitationIssuer()).getAmountOf(toTrade); | ||
t.like(amt, { value: [{ description: 'trade' }] }); | ||
}); |
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 is unnecessary to test for as it's not explicitly supported. Only passable arguments without promises are supported by the async flow membrane, the attenuated zcf should never see such a thing. Let's actually make the guard stricter than the original zcf and test that it refuses a function handler.
export const ZcfI = M.interface( | ||
'ZCF', | ||
{ | ||
makeInvitation: M.call(M.raw(), M.string()) |
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.
Given that the membrane does not support non-passable arguments, let's restrict to a handler in remotable form ?
makeInvitation: M.call(M.raw(), M.string()) | |
makeInvitation: M.call(M.remotable('OfferHandler'), M.string()) |
// TODO: figure out Guarded<ZcfForFlows> vs. ZcfForFlows | ||
const { unbondAndLiquidStake } = orchestrateAll(flows, { zcfForFlows }); |
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.
@turadg I'm still interested in help with the types here.
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 took a look and the best fix isn't obvious to me. Some of the problem may be going from guest to host and back.
I would try defining the interface as a Guest interface (akin to ZCF) and making that what the flow takes. Then define the Exo in terms of that using HostOf
.
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, I did something like that. Now it seems to be griping about brands etc. that come from getTerms()
-- their isMyIssuer
method returns a promise, and it wants vows for those. Clues?
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.
LGTM
@@ -26,6 +27,7 @@ export const ZcfForFlowsI = M.interface( | |||
* @param {VowTools} vowTools | |||
*/ | |||
export const prepareZcfForFlows = (zcf, zone, vowTools) => { | |||
/** @satisfies {HostInterface<ZcfForFlows>} */ |
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.
👌
some of the errors now are with the Host interface exposing methods that return promises. Should we excise those?
getTerms().issuers[0].isMyIssuer(): Promise<boolean>
makeEmptySeatKit(...).zcfSeat.getSubscriber(...): Promise<Subscriber<Allocation>>
I think otherwise they'll need durable watchers to become vows.
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.
Ugh yeah these will not be usable inside the membrane
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'm inclined to drop getTerms()
-- I don't see any need to delay calling it until a flow has started.
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.
excising getSubscriber
means wrapping zcfSeat. I suppose that's reasonably straightforward.
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.
Wait I'm confused, zcfSeat is a local or remote object?
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 type error does point to some runtime impact.
I just don't know how to remove methods from brands while preserving their identity, which is kinda the whole point of a brand
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 see. I think we can just remove them from the types so that they don't appear to be available. If someone goes and uses a method that the docs and static types say isn't there, they shouldn't be surprised that it fails. (This doesn't apply to when we need to attenuate powers, but that's not the case here. If the method worked we'd gladly include it.)
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 think we can just remove them from the types so that they don't appear to be available.
That was my original thought but the problem is that passing such an object back as argument where a zcf seat is expected would cause a type error because the object wouldn't be compatible
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 attenuated ZCF should just be different from the ZCF. It just doesn't get past as an argument
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.
@dtribble the problem is not ZCF but zcfSeat
in the result of makeEmptySeatKit
. Does that need to be passed back anywhere? Should some of its methods be usable from the flow?
fec4ab1
to
81b621a
Compare
@@ -78,6 +78,13 @@ export const makeOrchestrationFacade = ({ | |||
const [wrappedCtx] = prepareEndowment(subZone, 'endowments', [hostCtx]); | |||
const hostFn = asyncFlow(subZone, 'asyncFlow', guestFn); | |||
|
|||
deepMapObject( |
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.
nb: the usage here somewhat abuses the "map" idea. Kind of a deepAssert
or visitObject
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.
quite
packages/orchestration/src/types.ts
Outdated
export type ZcfTools<CT = Record<string, unknown>> = Pick< | ||
ZCF<CT>, | ||
'atomicRearrange' | 'assertUniqueKeyword' | ||
> & { | ||
makeInvitation: <R, A = undefined>( | ||
offerHandler: OfferHandler<ERef<R>, A>, | ||
description: string, | ||
customDetails?: object, | ||
proposalShape?: Pattern, | ||
) => Promise<Invitation<R, A>>; | ||
}; |
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.
let's use an actual interface. This doesn't need any parameterization because CT
(Custom Terms) will not be available.
export type ZcfTools<CT = Record<string, unknown>> = Pick< | |
ZCF<CT>, | |
'atomicRearrange' | 'assertUniqueKeyword' | |
> & { | |
makeInvitation: <R, A = undefined>( | |
offerHandler: OfferHandler<ERef<R>, A>, | |
description: string, | |
customDetails?: object, | |
proposalShape?: Pattern, | |
) => Promise<Invitation<R, A>>; | |
}; | |
export interface ZcfTools { | |
assertUniqueKeyword: ZCF['assertUniqueKeyword']; | |
atomicRearrange: ZCF['atomicRearrange']; | |
makeInvitation: ZCF['makeInvitation']; | |
} |
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.
Ah yeah, an explicit interface like this makes more sense for when we start deviating
packages/orchestration/src/types.ts
Outdated
export type ZcfTools<CT = Record<string, unknown>> = Pick< | ||
ZCF<CT>, | ||
'atomicRearrange' | 'assertUniqueKeyword' | ||
> & { | ||
makeInvitation: <R, A = undefined>( | ||
offerHandler: OfferHandler<ERef<R>, A>, | ||
description: string, | ||
customDetails?: object, | ||
proposalShape?: Pattern, | ||
) => Promise<Invitation<R, A>>; | ||
}; |
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.
Ah yeah, an explicit interface like this makes more sense for when we start deviating
packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md
Show resolved
Hide resolved
1f9acb0
to
d1e234f
Compare
- unit tests - zcfTester copied from @agoric/zoe - don't pass zcf thru context in examples - restrict makeInvitation handler to passable - regenerate baggage snapshots
4c26254
to
cb20148
Compare
closes: #9773
Description
Provide selected ZCF APIs for use in orchestration flows. For example: use vows for resumable promises.
Security Considerations
nothing new
Scaling Considerations
n/a
Documentation Considerations
Testing Considerations
Upgrade Considerations
n/a