-
Notifications
You must be signed in to change notification settings - Fork 212
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
chore(orchestration): endowments are membrane-friendly #9591
Conversation
Deploying agoric-sdk with Cloudflare Pages
|
switch (message['@type']) { | ||
case '/cosmos.staking.v1beta1.MsgDelegate': { | ||
if (message.amount.amount === '504') { | ||
throw Error('simulated packet timeout'); |
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 best way to communicate a failure here? (bridge in bootstrap test)
// FIXME should receive "simulated packet timeout" | ||
message: | ||
/syscall.callNow failed: device.invoke failed, see logs for details/, | ||
}, |
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 newly added test, despite the broken error propagation through runUtils, passes on current master
(but is failing 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.
Temporarily fixed via d77582b
b09aaf9
to
fedb393
Compare
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.
Excellent work. There was one or two issues that I think really need fixing (and some comments here that should be for later work).
NetworkShape.Vow$(M.arrayOf(ChainAmountShape)), | ||
), | ||
withdrawRewards: M.call().returns( | ||
NetworkShape.Vow$(M.arrayOf(ChainAmountShape)), |
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.
Use of NetworkShape.Vow$
seems odd, given that there is also VowShape
. Can VowShape
have an optional argument, e.g., VowShape(NetworkShape...
. That's pretty clunky, but then there's just one way to express.
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.
Is there any doc on what Vow$
is?
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 created this ticket to clean up interface guards for watchers: #9611
TLDR; agree we should be importing something less verbose from @agoric/vow
. We also might just go with VowShape
instead of Vow$
since the unwrapped shape will likely never be reached. I can't find a link atm but this topic came up in a recent PR review cc @turadg
@@ -16,9 +16,10 @@ import { | |||
} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; | |||
import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; | |||
import { makeTracer } from '@agoric/internal'; | |||
import { Shape as NetworkShape } from '@agoric/network'; |
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.
Having to rename Shape
seems bad. network
should not export just Shape
.
@@ -129,7 +130,7 @@ export const prepareCosmosOrchestrationAccountKit = ( | |||
undelegateWatcher: M.interface('undelegateWatcher', { | |||
onFulfilled: M.call(M.string()) | |||
.optional(M.arrayOf(M.undefined())) // empty context | |||
.returns(M.promise()), | |||
.returns(NetworkShape.Vow$(M.promise())), |
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 cannot parse this pattern at all.
Delegate: M.callWhen(ChainAddressShape, AmountArgShape).returns( | ||
InvitationShape, | ||
Delegate: M.call(ChainAddressShape, AmountArgShape).returns( | ||
M.promise(), |
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 be explicitly a promise for void
?
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.
Does the promise pattern include Vows? Otherwise shouldn't this be a Vow?
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.
For the invitationMakers
interfaces, the return promise unwraps to an InvitationShape
, not a VowShape
. The offerHandler of the Invitation is what needs to be a Vow.
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 be explicitly a promise for void?
That's not expressible as a pattern. It's not pass-invariant.
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.
(Nit: in typescript void
does not signify undefined
but rather a kind of don't care. I'll ignore that below.)
In is indeed not expressible as a pattern, but in this case not because of pass invariance. Patterns (as opposed to guards) can only enforce what they can immediately check, and a pattern cannot immediately check what the promise will eventually fulfill to.
The original, by using an M.callWhen
method guard with a returns(InvitationShape)
does enforce that Delegate
returns a promise for something that matches InvitationShape
, because it uses the callWhen
delay to validate the promise's fulfillment before passing it through. Likewise, within an M.callWhen
you can say M.await(FooShape)
as a parameter guard. If the argument is a promise, this will enforce that the promise fulfills to something that matches FooShape
before passing that fulfillment along as the argument to the raw method.
async getBalances() { | ||
throw Error('not yet implemented'); | ||
getBalances() { | ||
return asVow(() => Fail`not yet implemented`); |
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 have a reference to the associated bug to implement it, and probably TODO in the error message?
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.
Good idea, done: #9610
@@ -111,27 +112,23 @@ export const prepareOrchestratorKit = ( | |||
}, | |||
}, | |||
orchestrator: { | |||
/** @type {Orchestrator['getChain']} */ | |||
/** @type {PromiseToVow<Orchestrator['getChain']>} */ |
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.
Too much more and we are gonna see renamed to P2V
:)
chainHub.getChainInfo('agoric'), | ||
this.facets.makeLocalChainFacadeWatcher, | ||
), | ||
// @ts-expect-error Type 'Vow<Voidless>' is not assignable to type 'Vow<Chain<any>>'. |
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 there be a TODO/bug to fix these types?
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.
}); | ||
|
||
await t.throwsAsync( | ||
wd.executeOffer({ |
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 is the expected error and why? Seems like somethign we shoudl check the details of, given that there are so many layers that could fail
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 expected error was commented out at the end of the function (this is the typical place to assert error message values in an ava test).
Inspection of the error message is disabled due to this: https://github.com/Agoric/agoric-sdk/pull/9591/files#r1655705785 - still not quite sure the past way to propagate an error through this bridge device.
The test still serves value since the rejection is properly propagated - just the message is missing.
I have confidence about the layering via tests in bootstrapTests/orchestration.test.ts
, which show some "ABCI code: 5: error handling packet: see events for details", as well as unit tests for local-orch-acct.
E(this.state.account).getBalance(brand), | ||
this.facets.getBalanceWatcher, | ||
{ denom }, |
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.
any particular reason why you pass denom
in an embedded record rather than as a sole argument? Consistency? I check reflexively because it is more allocations :)
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.
but perhaps clearer
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.
Context is a single argument, so I’ve been treating it like an opts bag for consistency/readability.
This is good feedback, though. These changes worth considering as we get closer to completion cc @turadg
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.
... These changes worth considering as we get closer to completion ...
I hope any plans to re-consider code we've written are represented by open issues.
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.
Not worth a ticket so updated here: 69a6e21
}, | ||
getBalances() { | ||
throw new Error('not yet implemented'); | ||
return asVow(() => Fail`not yet implemented`); |
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.
Isn't there a helper already (as with Promise.reject
) for making and already-rejected vow?
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.
Not to my knowledge. I think we could also do :
watch(Promise.reject(Fail`...`))
But asVow
seems concise enough to me here
cb5f043
to
4149dd8
Compare
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.
time for a meeting; sharing what I've got so far
.eslintrc.cjs
Outdated
{ | ||
selector: "Identifier[name='heapVowE']", | ||
message: | ||
'heapVowE shortens vows to promises; instead use `E` from `@endo/far`', |
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 be "instead use E with watch()"?
@@ -0,0 +1,5 @@ | |||
import type { Vow } from '@agoric/vow'; | |||
|
|||
export type PromiseToVow<T> = T extends (...args: infer A) => Promise<infer R> |
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 see docs for PromiseToVow
and type tests - at least 1 positive and 1 negative.
The name seems long. Maybe it doesn't need to appear in many places?
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.
Please see tests added here: 2dcc26f. As always, thanks for careful review. Something was missed in the original implementation.
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.
"Missed" is maybe harsh. It would return never
if the target was not a Promise. The caller shouldn't be using it in the first place.
Now, it passes through all values and only transforms Promises to Vows.
IBCTransferOptionsShape, | ||
} from '../typeGuards.js'; |
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.
aside: we call them ThingShape
but put them in files called typeGuards
. In E, they were called Guards. (likewise guards in Monte).
Trying it out just to look at it: DenomGuard
vs. DenomShape
. Hm. Not much of an improvement.
But our dev audience might already be familiar with the notion by the name "guard":
The term is used with specific meaning in APL, Haskell, Clean, Erlang, occam, Promela, OCaml, Swift,[2] Python from version 3.10, and Scala programming languages.
-- Guard (computer science) - Wikipedia
executeTx: M.callWhen(M.arrayOf(M.record())).returns(M.arrayOf(M.record())), | ||
delegate: M.call(M.string(), AmountShape).returns(VowShape), | ||
undelegate: M.call(M.string(), AmountShape).returns(VowShape), | ||
withdraw: M.call(AmountShape).returns(NetworkShape.Vow$(PaymentShape)), |
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.
Network
is a distraction here.
I suggest doing something like const { Vow$ } = NetworkShape;
above.
* JsonSafe< | ||
* TypedJson<'/cosmos.staking.v1beta1.MsgUndelegateResponse'> |
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.
note to self: is this distinction also expressed in a test?
delegatorAddress: this.state.address.address, | ||
}), | ||
]), | ||
this.facets.extractFirstResultWatcher, |
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.
Is including Watcher
in the name worthwhile? It's starting to feel like extractFirstResultWatcherBehaviorDefinitionCodeSpecification
.
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 its verbose. This seems to be the pattern adopted elsewhere, so I ran with it. Technically it's a "watcher handler"
}, | ||
/** @type {OrchestrationAccount<any>['transferSteps']} */ | ||
/** @type {PromiseToVow<OrchestrationAccount<any>['transferSteps']>} */ |
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 usage is a good basis for a type test; that is: in the test, use these names as motivation.
getBalance: M.call(M.any()).returns(NetworkShape.Vow$(CoinShape)), | ||
getBalances: M.call().returns(NetworkShape.Vow$(M.arrayOf(CoinShape))), |
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.
again, Network
is a distraction here
return this.facets.holder.delegate(validatorAddress, ertpAmount); | ||
return watch( |
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.
note to self: is a test for this change included in this PR?
If not, what's our plan for testing 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.
the 1st line of the commit message exceeds conventional norms
I suggest changing feat(orchestration):
to chore:
. The feature that I would expect to see in release notes is local-orchestration-account.
@@ -54,8 +54,8 @@ export const prepareLocalChainFacade = ( | |||
makeAccount() { | |||
const { localChainInfo } = this.state; | |||
const lcaP = E(localchain).makeAccount(); | |||
// FIXME use watch() from vowTools | |||
return heapVowTools.watch(allVows([lcaP, E(lcaP).getAddress()]), { | |||
// @ts-expect-error E does not understand Vow pipelining |
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.
That seems like a devex issue that our customers may encounter.
IOU an issue. Bonus points if you beat me to it.
cc @Jovonni @mitdralla
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.
E does not understand Vow pipelining
I thought this was the case, but it doesn't seem to be. I was tricked by the linter at one point and this comment is not in the final revision. I can try to squash it out of the commit, but not sure it's worth the effort
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.
On further analysis, the original comment does appear to be true. In this particular instance, E(localchain).makeAccount
is a Promise, not a Vow, so pipelining works.
Here's a test that demonstrates the behavior: d7bf05d
4149dd8
to
2dcc26f
Compare
69a6e21
to
2bece3a
Compare
- allows us to reference the idealized API which typically returns Promises in code that returns Vows
…outTimestamp` is provided
…m shared `orchestrationAccountMethods` are resumable
- adds VowifyAll helper for satisfies types test
* Make smart wallet use `when` to accept a Vow for the result of an offer NOTE: this enables upgrade of the contract, NOT upgrade of the smart-wallet refs: #9308 Co-authored-by: Dean Tribble <tribble@agoric.com>
adds tests that demonstrate: 1. allVows supporting pipelining if the operand is a Promise 2. allVows (original E?) does not support pipelining if the operand is a Vow
2bece3a
to
100e0a3
Compare
closes: #XXXX
refs: #9449
refs: #9308
Description
Changes in this PR are related to #9449, primarily for
local-orchestration-account.js
andcosmos-orchestration-account.js
,service.js
, andorchestrator.js
. Attenuated versions of these kit are what the caller of...getChain('agoric' | 'cosmos').makeAccount()
receives.This PR also includes a change to
smart-wallet
so it understands how to unwrap offerResults that are Vows. This uses heapVowTools which means it does not cover resumability in the scenario ofsmart-wallet
upgrading - only the contracts that rely on it.Other small changes included changes:
PromiseToVow
andVowifyAll
type helpers, so we can still reference the idealized API spec in code that returns VowsgetTimeoutTimestampNS
logic that removes an unnecessary network call when the caller supplies valuesheapVowE
in resumable codeSecurity Considerations
N/A
Scaling Considerations
N/A
Documentation Considerations
N/A
Testing Considerations
Adds a test boot and unit tests for a rejected Delegate wallet offer, which is helpful for ensuring errors are properly propagated through the vow chain.
Upgrade tests would be helpful here, but we haven't started on these yet. I don't think we need those to land this PR, although they are certainly critical before release. Would love to pair with someone on this and learn more about how we do that.
Upgrade Considerations
This PR contains a change to smart-wallet.