-
Notifications
You must be signed in to change notification settings - Fork 245
ForwardSkipped and refactorings for retry #11307
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
Conversation
Deploying agoric-sdk with
|
Latest commit: |
bcecfdb
|
Status: | ✅ Deploy successful! |
Preview URL: | https://d0d6af8c.agoric-sdk.pages.dev |
Branch Preview URL: | https://309-forwardskipped.agoric-sdk.pages.dev |
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.
looks pretty good, but I'd like to understand some parts better before approving
and I question one red alert.
if (reference === currentChainReference) { | ||
await settlement.send(destination, amount); | ||
statusManager.forwarded(txHash); | ||
} |
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.
don't we want an early return
in this if
?
the agoric chain is also in the cosmos
namespace, right?
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 catch! I'll try to figure out why no tests failed. Maybe they did both send()
and transfer()
and ended up with the same final state.
getNobleICA: () => OrchestrationAccount<{ chainId: 'noble-1' }>; | ||
settlementAccount: Promise<OrchestrationAccount<{ chainId: 'agoric-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.
thinking out loud... we pass a callback to get the noble ICA, but a promise to get the settlementAccount. seems a little inconsistent. I suppose there are constraints that motivate the different approaches.
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.
Yeah, I share the concern. What I could do is refactor it also to a promise but I'm not sure it's better,
const nobleIcaPK = makePromiseKit();
if (baggage.has(NOBLE_ICA_BAGGAGE_KEY))
nobleIcaPK.resolve(baggage.get(NOBLE_ICA_BAGGAGE_KEY));
And then whenever it is set, also resolve it.
Maybe there's some "durably stored promise" kit abstraction we could make, but still I don't think it's worth it.
What I will do is at a comment justifying the disharmony.
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.
"durably stored promise" kit abstraction
That would be a VowKit, no?
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 meant a regular promise that gets durably stored, like is happening here. "durably stored promise result kit" may have been 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.
native promises cannot be durably stored. The design for durable promises puts them more in line with vows than promises.
I think I still don't understand what "durably stored promise result kit" means.
asyncFuncEagerWakers: [], | ||
asyncFuncEagerWakers: [ | ||
Object @Alleged: asyncFlow flow {}, | ||
], | ||
asyncFuncFailures: {}, | ||
flowForOutcomeVow: {}, | ||
flowForOutcomeVow: { | ||
'Alleged: VowInternalsKit vowV0': 'Alleged: asyncFlow flow', | ||
}, |
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: I don't exactly understand what asyncFuncEagerWakers
and flowForOutcomeVow
are, but I suppose these are compatible changes.
and we do have a relevant upgrade test: deploy HEAD; upgrade to support EVM destinations
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.
They are internal state for async flow.
This failure is one I haven't seen before:
This doesn't have any changes I can see causing that so I'll run again and see if it's a rare flake. |
6c028e5
to
5e4ad34
Compare
I think we need to lift the call to Current HEAD: agoric-sdk/packages/fast-usdc-contract/src/fast-usdc.contract.ts Lines 297 to 313 in f80bb26
|
const poolAccountSendPK = makePromiseKit<void>(); | ||
const poolAccountTransferPK = makePromiseKit<void>(); | ||
const settleAccountTransferPK = makePromiseKit<void>(); | ||
const settleAccountSendPK = makePromiseKit<void>(); | ||
const intermediateAccountTransferPK = makePromiseKit<void>(); | ||
const intermediateAccountDepositForBurnPK = makePromiseKit<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.
Trying to understand this change more. I think these should still be VowKit's?
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, because the need to provide the promises that the async-flow expects. Previously it was a vow handler in Settler, which didn't go through a membrane. Now it's an async flow which when integrated goes through a membrane but in this unit test does not.
I tried to document this in the comment above. Edit suggestions welcomed.
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.
Oh, the comment is below,
// These each have VResolver for "vow" resolver. The mocks actually
// deal in promises but the flow that awaits them expects that it's actually
// awaiting a vow (made by the membrane to look like a promise).
Thanks for the diagnosis. I'll try your work-around. Should we schedule this? |
5e4ad34
to
28553b6
Compare
No problem but I wouldn't call it a workaround - we must register info in And yes we might consider prioritizing #10602. I also just created #11310 which would help surface this issue. Instead of seeing Error: key agoric already registered in collection chainInfos, we'd see Error: chain not found: agoric |
086a7ea
to
c76fecc
Compare
c76fecc
to
4824f98
Compare
This pull request has been removed from the queue for the following reason: The merge conditions cannot be satisfied due to failing checks: You may have to fix your CI before adding the pull request to the queue again. |
_incidental_ ## Description I burned time debugging a failure in #11307 because the output lost the error message and the stack trace. It's particularly costly in multichain-testing because of Starship setup time, how slow the tests are to rerun. The problem was that `t.notThrowsAsync` called from outside the test function loses the message and stack trace. I've demonstrated this with a `devex.test.ts`. I think we're better off eschewing it since awaiting a rejected promise will fail in exactly the same cases. ### * Considerations no other
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.
Besides some general comments about the refactoring (to help me get situated), I am very skeptical that foregoing asyncFlow in the test is the right approach. I don't see a reason we couldn't use it.
); | ||
statusManager.forwarded(txHash, false); | ||
} | ||
void forwardFunds({ txHash, amount: fullValue, destination: dest }); |
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.
Maybe add a comment here that forwardFunds
does error handling so a void
here is safe?
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 call. That's generally what void
implies but it's worth being explicit, especially since it returns a vow and not a promise
// funds remain in `settlementAccount` and must be recovered via a | ||
// contract upgrade | ||
log('🚨 forward transfer rejected!', reason, txHash); |
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 assume this part will be updated in the follow-up ?
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.
yes, the PR that implements retry
{ | ||
currentChainReference, | ||
supportsCctp, | ||
log = makeTracer('FlowForwardFunds'), |
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.
FYI a new tracer not going through the membrane will result in flow replays to produce log output again. Is that intended?
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.
Helpful catch, thanks! Perhaps we should avoid default args in flow params?
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.
Default args made from other args would be fine. I think the tell-tale sign here is that makeTracer
is in the surrounding scope and is not a pure function. Logging in general can be considered exempt from the closed over rules, but in this case I don't think it should.
timeout: FORWARD_TIMEOUT.p, | ||
}, | ||
}); | ||
log('forward retry successful for', txHash); |
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 can be a first time forward attempt too, no ? Actually right now without retry it can only be a first time. Why "retry" ?
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.
From when the scope of the change was the retry feature instead of just a refactor. I'll revise
// update status manager, flagging a terminal state that needs manual | ||
// intervention or a code update to remediate | ||
statusManager.forwardFailed(txHash); |
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 know this is just duplication of the code in depositForBurnHandler.onRejected
, but this seems like a different failed state that the others where the funds are no longer in the local chain. I assume this will require a different retry mechanism?
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.
yes. retry is out of scope for this PR
} | ||
} else { | ||
log( | ||
'⚠️ forward not attempted', |
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.
Why is this just a warning, this feels more fatal than the failed forwards (which will soon be retried)
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.
because the siren is for stuff that should never happen and someone sending a bad EUD is not worth waking anyone
const forwardFunds = tx => | ||
flows.forwardFunds( | ||
undefined as any, | ||
{ | ||
log, // some tests check the log calls | ||
currentChainReference: 'agoric-3', | ||
supportsCctp: makeSupportsCctp(chainHub), | ||
statusManager, | ||
getNobleICA: () => mockAccounts.intermediate.account as any, | ||
settlementAccount: Promise.resolve( | ||
mockAccounts.settlement.account as any, | ||
), | ||
}, | ||
tx, | ||
); | ||
|
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 don't understand why orchestrate
as created by makeOrchestrationFacade
wouldn't work and be better fidelity.
There doesn't seem to be any mock endowments incompatible with async-flow, and it would avoid impedance mismatch between real objects (e.g. statusManager), and the flow's logic.
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 don't think anyone is claiming it wouldn't work or be higher fidelity. Those aren't the only two criteria.
I'm trying to ship features to users. This is a test. It's testing what I need it to.
asyncFuncEagerWakers: [], | ||
asyncFuncEagerWakers: [ | ||
Object @Alleged: asyncFlow flow {}, | ||
], | ||
asyncFuncFailures: {}, | ||
flowForOutcomeVow: {}, | ||
flowForOutcomeVow: { | ||
'Alleged: VowInternalsKit vowV0': 'Alleged: asyncFlow flow', | ||
}, |
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.
They are internal state for async flow.
4e311a6
to
2ce7b18
Compare
This pull request has been removed from the queue for the following reason: The merge conditions cannot be satisfied due to failing checks: You may have to fix your CI before adding the pull request to the queue again. |
2ce7b18
to
3ba9563
Compare
closes: #11309 refs: #11307 ## Description This attenuator implementation is just a simplification of the revocable kit implementation in prepare-revocable.js. The revocable kit provides both attenuation and revocation, since we can have both for the price of just one level of indirection. But if you don't need revocation, the revocable kit has more API complexity than you need. Because the attenuator just uses an exo class rather than a class kit, it cannot support the privilege separation of a distinct revoker facet. But it can still support `selfRevoke` as a separate `extraMethod`, as shown in a testcase at `prepare-attenuator.test.js`. - [x] Understand the `attenuate` in `@agoric/internal`, and, if possible, reconcile them. Or deprecate one in favor of the other. - That `attenuate` is quite different. It is more of a deep pick appropriate for own data properties, or very specifically for own non-this-sensitive methods. The attenuator it creates necessarily has own properties for these deeply picked properties. Whereas, this PR creates an exo attenuator with the exposed properties on its own exo class. It is built assuming that the underlying is an exo, but it would probably work for a far object as well. The key is it makes no assumption about where in the underlying's inheritance chain the method is found. The attenuator in this PR has no support for data properties. - [x] Similarly, understand the `prepareAttenuator` in `@agoric/internal`. - That `prepareAttenuator` is similar in many ways to the attenuator in this PR. However, it depends on a `Callback` concept that IMO makes it significantly harder to understand. What extra expressiveness to we get for that extra complexity? Do we use any of that extra expressiveness? I don't know. To be investigated after this PR. Attn @michaelfig ### Security Considerations Attenuation helps POLA, which helps security. ### Scaling Considerations As with revocation, the cost of attenuation is one more level of indirection. So, for most purposes, none. In particular, a concern was raised about attenuators having per-instance methods as opposed to per-class methods. As with revocation, this implementation uses only per-class methods. ### Documentation Considerations Both revocation and attenuation need to be documented. ### Testing Considerations Attenuation testing included, based on the existing revocation tests. ### Upgrade Considerations Done as prepare patterns, so should be independently upgradable. Including change to what methods are included and what `extraMethods` are added. Note that when `@agoric/base-zone` moves to endo, this attenuator should move with it. The meaning is clearly as useful for non-agoric endo users. Attn @kriskowal
3ba9563
to
bcecfdb
Compare
closes: https://github.com/Agoric/agoric-private/issues/309
Description
This adds a ForwardSkipped state per https://github.com/Agoric/agoric-private/issues/309
It also has a series of refactorings to prepare for https://github.com/Agoric/agoric-private/issues/311
Reviewers,
Security Considerations
none
Scaling Considerations
none
Documentation Considerations
none
Testing Considerations
CI
Upgrade Considerations
This will be part of the CCTP beta and must successfully upgrade in Mainnet. The RunUtils test does upgrade and already caught an intermediate bug (omitting the obsolete handler facets) which I've documented.