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

chore(orchestration): endowments are membrane-friendly #9591

Merged
merged 19 commits into from
Jun 30, 2024

Conversation

0xpatrickdev
Copy link
Member

@0xpatrickdev 0xpatrickdev commented Jun 26, 2024

closes: #XXXX
refs: #9449
refs: #9308

Description

Changes in this PR are related to #9449, primarily for local-orchestration-account.js and cosmos-orchestration-account.js, service.js, and orchestrator.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 of smart-wallet upgrading - only the contracts that rely on it.

Other small changes included changes:

  • PromiseToVow and VowifyAll type helpers, so we can still reference the idealized API spec in code that returns Vows
  • fix/refactor of getTimeoutTimestampNS logic that removes an unnecessary network call when the caller supplies values
  • lint rule: adds a warning when importing heapVowE in resumable code
  • resumable lint rule changes to error, now that all warnings are resolved

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

Copy link

cloudflare-workers-and-pages bot commented Jun 26, 2024

Deploying agoric-sdk with  Cloudflare Pages  Cloudflare Pages

Latest commit: 100e0a3
Status: ✅  Deploy successful!
Preview URL: https://24abe6ef.agoric-sdk.pages.dev
Branch Preview URL: https://pc-local-orch-account-vows.agoric-sdk.pages.dev

View logs

switch (message['@type']) {
case '/cosmos.staking.v1beta1.MsgDelegate': {
if (message.amount.amount === '504') {
throw Error('simulated packet timeout');
Copy link
Member Author

@0xpatrickdev 0xpatrickdev Jun 27, 2024

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/,
},
Copy link
Member Author

@0xpatrickdev 0xpatrickdev Jun 27, 2024

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)

Copy link
Member Author

@0xpatrickdev 0xpatrickdev Jun 27, 2024

Choose a reason for hiding this comment

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

Temporarily fixed via d77582b

@0xpatrickdev 0xpatrickdev force-pushed the pc/local-orch-account-vows branch 3 times, most recently from b09aaf9 to fedb393 Compare June 27, 2024 20:53
@0xpatrickdev 0xpatrickdev marked this pull request as ready for review June 27, 2024 22:20
Copy link
Member

@dtribble dtribble left a 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)),
Copy link
Member

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.

Copy link
Member

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?

Copy link
Member Author

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';
Copy link
Member

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())),
Copy link
Member

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(),
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 be explicitly a promise for void?

Copy link
Member

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?

Copy link
Member Author

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.

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 be explicitly a promise for void?

That's not expressible as a pattern. It's not pass-invariant.

Copy link
Member

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`);
Copy link
Member

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?

Copy link
Member Author

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']>} */
Copy link
Member

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>>'.
Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I added cb5f043 which clearly spells out the compromises I made on types. I suspect the PromiseVow added here doesn't get us all the way with generics needed for getChain.

For the ticket, I am attributing it is part of the remaining work for #9449.

});

await t.throwsAsync(
wd.executeOffer({
Copy link
Member

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

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 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 },
Copy link
Member

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

Copy link
Member

Choose a reason for hiding this comment

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

but perhaps clearer

Copy link
Member Author

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

Copy link
Member

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.

Copy link
Member Author

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`);
Copy link
Member

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?

Copy link
Member Author

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

@0xpatrickdev 0xpatrickdev added the force:integration Force integration tests to run on PR label Jun 28, 2024
@0xpatrickdev 0xpatrickdev force-pushed the pc/local-orch-account-vows branch 2 times, most recently from cb5f043 to 4149dd8 Compare June 28, 2024 16:33
Copy link
Member

@dckc dckc left a 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`',
Copy link
Member

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>
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 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?

Copy link
Member Author

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.

Copy link
Member Author

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';
Copy link
Member

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)),
Copy link
Member

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.

Comment on lines +188 to +180
* JsonSafe<
* TypedJson<'/cosmos.staking.v1beta1.MsgUndelegateResponse'>
Copy link
Member

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,
Copy link
Member

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.

Copy link
Member Author

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']>} */
Copy link
Member

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.

Comment on lines 12 to 13
getBalance: M.call(M.any()).returns(NetworkShape.Vow$(CoinShape)),
getBalances: M.call().returns(NetworkShape.Vow$(M.arrayOf(CoinShape))),
Copy link
Member

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

Comment on lines -164 to +153
return this.facets.holder.delegate(validatorAddress, ertpAmount);
return watch(
Copy link
Member

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?

Copy link
Member

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
Copy link
Member

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

Copy link
Member Author

@0xpatrickdev 0xpatrickdev Jun 28, 2024

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

Copy link
Member Author

@0xpatrickdev 0xpatrickdev Jun 28, 2024

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

@0xpatrickdev 0xpatrickdev removed the force:integration Force integration tests to run on PR label Jun 28, 2024
@0xpatrickdev 0xpatrickdev changed the title feat(orchestration): endowments are membrane-friendly chore(orchestration): endowments are membrane-friendly Jun 29, 2024
@0xpatrickdev 0xpatrickdev added the force:integration Force integration tests to run on PR label Jun 29, 2024
@dtribble dtribble added the automerge:rebase Automatically rebase updates, then merge label Jun 30, 2024
0xpatrickdev and others added 10 commits June 30, 2024 02:10
* 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
@mergify mergify bot merged commit efd3370 into master Jun 30, 2024
74 checks passed
@mergify mergify bot deleted the pc/local-orch-account-vows branch June 30, 2024 02:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge:rebase Automatically rebase updates, then merge force:integration Force integration tests to run on PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants