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

fix(orchestration): subscribeToTransfers() atomically #10553

Merged
merged 2 commits into from
Nov 25, 2024
Merged

Conversation

dckc
Copy link
Member

@dckc dckc commented Nov 22, 2024

refs: #10391

Description

There was a race in subscribeToTransfers. Use a Vow to avoid the race.

Security / Scaling / Upgrade / Documentation Considerations

can't think of any

Testing Considerations

I'm not sure how to make a test for this. The purpose of this fix is mostly to stop other tests from failing.

@dckc dckc requested a review from a team as a code owner November 22, 2024 17:36
@0xpatrickdev
Copy link
Member

These are two tests we have that cover this path (via auto-stake-it.contract.js / auto-stake-it.flows.js) :

I assume they don't have the same symptoms we are currently facing due to these async calls in between .makeAccount() and .monitorTransfer():

const [localAccount, stakingAccount] = await Promise.all([
agoric.makeAccount(),
/** @type {Promise<OrchestrationAccount<any> & StakingAccountActions>} */ (
remoteChain.makeAccount()
),
]);
const [localChainAddress, remoteChainAddress] = await Promise.all([
localAccount.getAddress(),
stakingAccount.getAddress(),
]);
const agoricChainId = (await agoric.getChainInfo()).chainId;
const { transferChannel } = await chainHub.getConnectionInfo(
agoricChainId,
chainId,
);
assert(transferChannel.counterPartyChannelId, 'unable to find sourceChannel');
const localDenom = `ibc/${denomHash({ denom: remoteDenom, channelId: transferChannel.channelId })}`;
// Every time the `localAccount` receives `remoteDenom` over IBC, delegate it.
const tap = makeStakingTap({
localAccount,
stakingAccount,
validator,
localChainAddress,
remoteChainAddress,
sourceChannel: transferChannel.counterPartyChannelId,
remoteDenom,
localDenom,
});
// XXX consider storing appRegistration, so we can .revoke() or .updateTargetApp()
// @ts-expect-error tap.receiveUpcall: 'Vow<void> | undefined' not assignable to 'Promise<any>'
await localAccount.monitorTransfers(tap);

But their passing should help towards giving us confidence this change does not introduce a regression.

Copy link

cloudflare-workers-and-pages bot commented Nov 22, 2024

Deploying agoric-sdk with  Cloudflare Pages  Cloudflare Pages

Latest commit: 7b77993
Status: ✅  Deploy successful!
Preview URL: https://da154411.agoric-sdk.pages.dev
Branch Preview URL: https://dc-monitor-race.agoric-sdk.pages.dev

View logs

@dckc dckc force-pushed the dc-monitor-race branch 2 times, most recently from 9d52108 to 29fad09 Compare November 22, 2024 17:59
@dckc dckc requested a review from iomekam November 22, 2024 18:00
Copy link
Member

@turadg turadg left a comment

Choose a reason for hiding this comment

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

I don't know all the possible ramifications of this change so I don't feel qualified to Approve. I think we need @iomekam or @michaelfig who I think made packet-tools.

@dckc dckc removed the request for review from 0xpatrickdev November 22, 2024 19:26
@michaelfig
Copy link
Member

I think we need @iomekam or @michaelfig who I think made packet-tools.

Guilty as charged! (Thanks for the ping, I'm taking a look now.)

Copy link
Member

@michaelfig michaelfig left a comment

Choose a reason for hiding this comment

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

I recommend a different tactic that can robustly avoid the race. Let me know what you think.

@@ -340,6 +340,7 @@ export const preparePacketTools = (zone, vowTools) => {
const { tap } = this.facets;
// XXX racy; fails if subscribeToTransfers is called while this promise is in flight
// e.g. 'Target "agoric1fakeLCAAddress" already registered'
this.state.reg = 'RACING';
Copy link
Member

Choose a reason for hiding this comment

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

Here's how I would solve the race: check for the cached registration, and if it's not present, atomically replace it with a vow.

        subscribeToTransfers() {
          // Subscribe to the transfers for this account.
          const { lca, reg: cachedReg } = this.state;
          if (cachedReg) {
            return when(cachedReg);
          }
          // Atomically update the registration.
          const { tap } = this.facets;
          const reg = watch(E(lca).monitorTransfers(tap));
          this.state.reg = reg;
          return when(reg);
        },

Copy link
Member

Choose a reason for hiding this comment

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

Oh, and the FIXME comment on line 330-331 is no longer needed:

          // FIXME when it returns undefined this causes an error:
          // In "unsubscribeFromTransfers" method of (PacketToolsKit utils): result: undefined "[undefined]" - Must be a promise

It was solved 3 months ago by @0xpatrickdev changing decrPendingPatterns's method guard to use .returns(M.undefined()).

@@ -126,7 +126,7 @@ export const preparePacketTools = (zone, vowTools) => {
const resolverToPattern = zone.detached().mapStore('resolverToPattern');
return {
lca,
reg: /** @type {Remote<TargetRegistration> | null} */ (null),
reg: /** @type {Remote<TargetRegistration> | 'RACING' | null} */ (null),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
reg: /** @type {Remote<TargetRegistration> | 'RACING' | null} */ (null),
reg: /** @type {Vow<Remote<TargetRegistration>> | null} */ (null),

@dckc dckc changed the title fix!(orchestration): expose subscribeToTransfers() race to caller fix!(orchestration): subscribeToTransfers() atomically Nov 25, 2024
@dckc dckc changed the title fix!(orchestration): subscribeToTransfers() atomically fix(orchestration): subscribeToTransfers() atomically Nov 25, 2024
@dckc dckc added the automerge:rebase Automatically rebase updates, then merge label Nov 25, 2024
@mergify mergify bot merged commit 8fd731c into master Nov 25, 2024
95 checks passed
@mergify mergify bot deleted the dc-monitor-race branch November 25, 2024 19:41
mergify bot added a commit that referenced this pull request Nov 25, 2024
closes: #10391

## Description

 - feat(fast-usdc): settler disburses or forwards funds

Discussion of what to do if the minted USDC shows up at each state led to some refinement of states.
So the scope of this PR expanded somewhat:

 - chore(fast-usdc): status manager: split out Advancing state
 - chore(fast-usdc): advancer: split ADVANCING state


### Security Considerations

In addition to the normal case where funds are repaid to the pool and fees are distributed, the settler is responsible to forward funds in case they were not advanced.

### Scaling Considerations

nothing novel

### Documentation Considerations

Readers are assumed to be familiar with design docs.

### Testing Considerations

Getting the tests to pass requires:

 - [ ] #10553

It's included in this PR for now but is expected to land separately

DRAFT until
 - [x] test "Settlement for unknown transaction" case

### Upgrade Considerations

This is a new component.
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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants