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(Dataworker): Account for total required netSendAmount when executing PoolRebalanceLeaves #1933

Merged
merged 68 commits into from
Dec 16, 2024

Conversation

nicholaspai
Copy link
Member

@nicholaspai nicholaspai commented Nov 28, 2024

Context

The dataworker executor functionality is supposed to detect when to call sync() before executing L1 PoolRebalance and RelayerRefund leaves depending on the liquidReserves value of l1 tokens before executing those leaves.

There are two important areas where we might want to call sync before executing any pool rebalance leaves:

  1. First, we check if we should call sync when executing the Ethereum pool rebalance leaf and the Ethereum relayer refund leaves. Pool rebalance leaves subtract netSendAmount from liquidReserves while refund leaves add amountToReturn to liquid reserves (because Ethereum refund leaves are on the same chain as the hub pool, this amountToReturn is transferred atomically). This step is done here.
  2. Secondly, we check if we need to call sync before executing the non-Ethereum pool rebalance leaves. These all will subtract their netSendAmounts from the HubPool. This step subtly takes in any updated liquid reserves value from the first step if we executed a sync pre-execution of the Ethereum pool rebalance leaves. It also is aware of any used balance from netSendAmounts in the first step via the BalanceAllocator.

Problem

In the second step where we call _updateExchangeRatesBeforeExecutingNonHubChainLeaves we are not evaluating the total netSendAmount obligation of all non-Ethereum PoolRebalance leaves we want to execute. This means we can submit a transaction to execute a set of pool leaves whose netSendAmounts might sum greater than the liquidReserves (plus any BalanceAllocator adjustments), but individually the netSendAmounts are all less than liquidReserves. This batch transaction would then fail in simulation.

Examples of problems

We saw a bundle of PoolRebalanceLeaves today fail to execute because three of the leaves, one Ethereum leaf and two non-Ethereum leaves, had a total netSendAmount greater than the HubPool's liquidReserves, but each individually had a netSendAmount < the liquidReserves. For example, the three leaves had netSendAmounts of:

  • 40
  • 90
  • 70

While the hubPool's liquidReserves was 180:

  • 40 + 90 + 70 = 200 > 180
  • 40 < 180
  • 90 < 180
  • 70 < 180

If you take these numbers and run them through the executePoolRebalanceLeaves code above, you'll see how a PoolRebalanceLeaf execution was able to be submitted but then fail in simulation, without preceding the non-Ethereum pool rebalance leaf executions with a sync transaction.

PR side effects

This PR also refines the logic within the various internal functions called by executePoolRebalanceLeaves:

  • _updateExchangeRatesBeforeExecutingHubChainLeaves: enqueues a sync call if necessary to execute any hub chain pool leaves
  • _executePoolRebalanceLeaves: submits executeRootBundle calls but also adds any loadEthForL2Calls/transfer calls required to pay for an Arbitrum orbit L1->L2 message
  • _updateExchangeRatesBeforeExecutingNonHubChainLeaves: enqueues a sync call if necessary to execute any non-hub chain pool leaves. Useful after _updateExchangeRatesBeforeExecutingHubChainLeaves which might pull Ethereum_SpokePool funds into the HubPool that need to be synced right before executing non-Hubchain leaves
  • Various Dataworker logs are refined to be more helpful. For example, the blockRangesAreInvalid warning will now indicate which chain specifically requires a longer lookback.

Unit tests are updated as well

…RebalanceLeaves

## Context

The dataworker executor functionality is supposed to detect when to call `sync()` before executing L1 PoolRebalance and RelayerRefund leaves depending on the `liquidReserves` value of l1 tokens before executing those leaves.

We use pass around the `balanceAllocator` when simulating execution of the [PoolRebalanceLeaves](https://github.com/across-protocol/relayer/blob/3e9c1e4108568b39fe007f8fcd71721db4bbe090/src/dataworker/Dataworker.ts#L1491) and the [RelayerRefundLeaves](https://github.com/across-protocol/relayer/blob/3e9c1e4108568b39fe007f8fcd71721db4bbe090/src/dataworker/Dataworker.ts#L1512) in order to keep track of how many L1 tokens are withdrawn from and deposited to the HubPool following Hub-chain leaf executions.

This way, we can use the `balanceAllocator` in [this function](https://github.com/across-protocol/relayer/blob/3e9c1e4108568b39fe007f8fcd71721db4bbe090/src/dataworker/Dataworker.ts#L1531) to detect when we're not going to have enough funds in LP reserves to execute a leaf [here](https://github.com/across-protocol/relayer/blob/3e9c1e4108568b39fe007f8fcd71721db4bbe090/src/dataworker/Dataworker.ts#L1823).

## Problem

The problem is that when accounting for PoolRebalanceLeaf executions, we were ADDING not subtracting balance to the balanceAllocator's count of the hubpool's reserves. This means that if the current `liquidReserves` were good enough to cover execution of the Ethereum PoolRebalanceLeaf [here](https://github.com/across-protocol/relayer/blob/3e9c1e4108568b39fe007f8fcd71721db4bbe090/src/dataworker/Dataworker.ts#L1484), then the balance allocator would accidentally inflate the HubPool's balance [here](https://github.com/across-protocol/relayer/blob/3e9c1e4108568b39fe007f8fcd71721db4bbe090/src/dataworker/Dataworker.ts#L1488).

This function [here](https://github.com/across-protocol/relayer/blob/3e9c1e4108568b39fe007f8fcd71721db4bbe090/src/dataworker/Dataworker.ts#L1529C40-L1529C92) would then have issues. Within this function, if any individual PoolRebalanceLeaf's `netSendAmount` was less than its liquid reserves, then a `sync` would be skipped [here](https://github.com/across-protocol/relayer/blob/3e9c1e4108568b39fe007f8fcd71721db4bbe090/src/dataworker/Dataworker.ts#L1802). This [line](https://github.com/across-protocol/relayer/blob/3e9c1e4108568b39fe007f8fcd71721db4bbe090/src/dataworker/Dataworker.ts#L1822) would then artificially inflate the hub pool's balance in the balance allocator, leading to a much more down stream simulation error when the pool rebalance leaf execution fails for unknown reasons.

## Examples of problems

We saw a bundle of PoolRebalanceLeaves today fail to execute because three of the leaves, one Ethereum leaf and two non-Ethereum leaves, had a total `netSendAmount` greater than the HubPool's `liquidReserves`, but each individually had a `netSendAmount` < the `liquidReserves`. For example, the three leaves had `netSendAmounts` of:
- 40
- 90
- 70

While the hubPool's liquidReserves was 180:
- 40 + 90 + 70 = 200 > 180
- 40 < 180
- 90 < 180
- 70 < 180

If you take these numbers and run them through the `executePoolRebalanceLeaves` code above, you'll see how a PoolRebalanceLeaf execution was able to be submitted but then fail in simulation, without preceding the leaf executions with a `sync` transaction.
@nicholaspai nicholaspai marked this pull request as draft November 28, 2024 17:03
@nicholaspai nicholaspai changed the title fix(Dataworker): Update balanceAllocator properly when executing PoolRebalanceLeaves fix(Dataworker): Account for total required netSendAmount when executing PoolRebalanceLeaves Nov 28, 2024
@nicholaspai nicholaspai marked this pull request as ready for review November 28, 2024 18:03
Copy link
Member Author

@nicholaspai nicholaspai left a comment

Choose a reason for hiding this comment

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

Moving to draft stage as I think we can simplify this logic more #1933 (comment)

I've been thinking for a couple days now that something feels weird about including the balance allocator logic when executing pool leaves. It works when executing spoke leaves because spokes don't have a separate accounting of "liquid reserves"--all ERC20 balances are used to execute leaves, whereas the hub pool has a special accounting method.

src/dataworker/Dataworker.ts Outdated Show resolved Hide resolved
@nicholaspai nicholaspai added the do not merge Don't merge until label is removed label Nov 29, 2024
@nicholaspai nicholaspai marked this pull request as draft November 30, 2024 05:17
Copy link
Contributor

@bmzig bmzig left a comment

Choose a reason for hiding this comment

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

I think this makes sense. I also think the balanceAllocator/liquid reserves tracking in the executor is complicated, but in general, this looks to, at worst, add extra calls and log more info, so I think it's worth trying.

@nicholaspai nicholaspai requested a review from mrice32 December 6, 2024 20:21
src/clients/BalanceAllocator.ts Outdated Show resolved Hide resolved
src/dataworker/DataworkerUtils.ts Outdated Show resolved Hide resolved
src/dataworker/Dataworker.ts Outdated Show resolved Hide resolved
@nicholaspai nicholaspai requested a review from pxrl December 15, 2024 22:47
src/dataworker/DataworkerUtils.ts Show resolved Hide resolved
@nicholaspai nicholaspai merged commit 79c6f70 into master Dec 16, 2024
4 checks passed
@nicholaspai nicholaspai deleted the dataworker-executor-l1-sync branch December 16, 2024 20:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants