Skip to content

Add a timeout to balance override detection#4317

Merged
jmg-duarte merged 4 commits intomainfrom
jmgd/fix/sload
Apr 9, 2026
Merged

Add a timeout to balance override detection#4317
jmg-duarte merged 4 commits intomainfrom
jmgd/fix/sload

Conversation

@jmg-duarte
Copy link
Copy Markdown
Contributor

@jmg-duarte jmg-duarte commented Apr 8, 2026

Description

Balance override detection can stall the quote pipeline when probing certain tokens. Reflection tokens (e.g. LuckyBlock) implement balanceOf by iterating over a storage array in _getReflectionRate(). During strategy verification, we override storage slots with a test value — if that value lands on an array-length slot, the EVM loops until the node's execution timeout.

This adds a configurable timeout around the full detect() call in cached_detection to prevent slow tokens from blocking quote processing. The default is 1 second.

Changes

  • Add detection_timeout field to BalanceOverrides with a default of 1s
  • Wrap detector.detect() in tokio::time::timeout inside cached_detection, treating timeouts as DetectionError::NotFound (which gets cached to avoid retrying)
  • Add detection-timeout-secs config field to BalanceOverridesConfig so the timeout is tunable per deployment

How to test

Staging


Before applying this

jmgduarte@unknown5e8ed84873b2 ~/D/c/infrastructure (staging)> time curl --location 'https://barn.api.cow.fi/bnb/api/v1/quote' \
                                                                  --header 'Content-Type: application/json' \
                                                                  --data '{
                                                                  "sellToken": "0x2cd96e8c3ff6b5e01169f6e3b61d28204e7810bb",
                                                                  "buyToken": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
                                                                  "from": "0x4EF880525383ab4E3d94b7689e3146bF899A296e",
                                                                  "receiver": "0x4EF880525383ab4E3d94b7689e3146bF899A296e",
                                                                  "sellAmountBeforeFee": "243443381571330",
                                                                  "kind": "sell",
                                                                  "priceQuality": "optimal",
                                                                  "validFor": 300,
                                                                  "appData": "{\"appCode\":\"OneKey CowSwap\",\"environment\":\"production\",\"metadata\":{\"orderClass\":{\"orderClass\":\"market\"},\"partnerFee\":{\"recipient\":\"0x09DDb1031A31Ac82Ef276AB8Ea67c0941ef27c02\",\"volumeBps\":30},\"quote\":{\"slippageBips\":62,\"smartSlippage\":false},\"referrer\":{\"address\":\"0x09DDb1031A31Ac82Ef276AB8Ea67c0941ef27c02\"}},\"version\":\"1.6.0\"}",
                                                                  "appDataHash": "0x8bc9701f53009567e798a5b879ca06aa849e4b10ada621b7f2d147f9e411d4d8",
                                                                  "timeout":6000
                                                              }'
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>504 Gateway Timeout ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront) HTTP3 Server
Request ID: R7hFhywd1QB3QiA7xso829L_FErAgjEvZXCpY3fNa_SGX19Flxfq4g&#x3D;&#x3D;
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>
________________________________________________________
Executed in   30.20 secs      fish           external
   usr time   16.68 millis    0.41 millis   16.28 millis
   sys time   15.25 millis    1.99 millis   13.27 millis

After applying this

jmgduarte@unknown5e8ed84873b2 ~/D/c/infrastructure (staging)> time curl --location 'https://barn.api.cow.fi/bnb/api/v1/quote' \
                                                                  --header 'Content-Type: application/json' \
                                                                  --data '{
                                                                  "sellToken": "0x2cd96e8c3ff6b5e01169f6e3b61d28204e7810bb",
                                                                  "buyToken": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
                                                                  "from": "0x4EF880525383ab4E3d94b7689e3146bF899A296e",
                                                                  "receiver": "0x4EF880525383ab4E3d94b7689e3146bF899A296e",
                                                                  "sellAmountBeforeFee": "243443381571330",
                                                                  "kind": "sell",
                                                                  "priceQuality": "optimal",
                                                                  "validFor": 300,                                                                                                              "appData": "{\"appCode\":\"OneKey CowSwap\",\"environment\":\"production\",\"metadata\":{\"orderClass\":{\"orderClass\":\"market\"},\"partnerFee\":{\"recipient\":\"0x09DDb1031A31Ac82Ef276AB8Ea67c0941ef27c02\",\"volumeBps\":30},\"quote\":{\"slippageBips\":62,\"smartSlippage\":false},\"referrer\":{\"address\":\"0x09DDb1031A31Ac82Ef276AB8Ea67c0941ef27c02\"}},\"version\":\"1.6.0\"}",                                                                                                                          "appDataHash": "0x8bc9701f53009567e798a5b879ca06aa849e4b10ada621b7f2d147f9e411d4d8",
                                                                  "timeout":6000
                                                              }'
{"quote":{"sellToken":"0x2cd96e8c3ff6b5e01169f6e3b61d28204e7810bb","buyToken":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","receiver":"0x4ef880525383ab4e3d94b7689e3146bf899a296e","sellAmount":"101408548709609","buyAmount":"19632333521561","validTo":1775660168,"appData":"{\"appCode\":\"OneKey CowSwap\",\"environment\":\"production\",\"metadata\":{\"orderClass\":{\"orderClass\":\"market\"},\"partnerFee\":{\"recipient\":\"0x09DDb1031A31Ac82Ef276AB8Ea67c0941ef27c02\",\"volumeBps\":30},\"quote\":{\"slippageBips\":62,\"smartSlippage\":false},\"referrer\":{\"address\":\"0x09DDb1031A31Ac82Ef276AB8Ea67c0941ef27c02\"}},\"version\":\"1.6.0\"}","appDataHash":"0x8bc9701f53009567e798a5b879ca06aa849e4b10ada621b7f2d147f9e411d4d8","feeAmount":"142034832861721","gasAmount":"235614","gasPrice":"52500001","sellTokenPrice":"0.0870894483162219235072853962265071459114551544189453125","kind":"sell","partiallyFillable":false,"sellTokenBalance":"erc20","buyTokenBalance":"erc20","signingScheme":"eip712"},"from":"0x4ef880525383ab4e3d94b7689e3146bf899a296e","expiration":"2026-04-08T14:53:08.775181109Z","id":24317,"verified":false,"protocolFeeBps":"2"}
________________________________________________________
Executed in    1.51 secs      fish           external
   usr time   14.70 millis    1.25 millis   13.45 millis
   sys time   12.12 millis    3.78 millis    8.34 millis

@jmg-duarte jmg-duarte added the hotfix Labels PRs that should be applied into production right away label Apr 8, 2026
@jmg-duarte jmg-duarte marked this pull request as ready for review April 8, 2026 15:42
@jmg-duarte jmg-duarte requested a review from a team as a code owner April 8, 2026 15:42
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a configurable timeout for balance override detection to prevent slow token implementations from stalling the quote pipeline. A critical issue was identified regarding the interaction between the timeout and batched RPC requests; specifically, a timeout on a single token could cause the entire batch to be incorrectly marked as not found and cached. To prevent these false negatives, it is recommended to return None on timeout instead of an error, thereby skipping the caching logic for that specific instance.

@jmg-duarte jmg-duarte requested a review from MartinquaXD April 8, 2026 16:55
Copy link
Copy Markdown
Contributor

@MartinquaXD MartinquaXD left a comment

Choose a reason for hiding this comment

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

Because all verification calls are batched into a single JSON-RPC request by the ethrpc transport layer, one slow call blocks the entire batch.

This is not entirely correct btw. The current code checks overrides serially in a loop. That prevents those calls from landing in the same batch. (i.e. second call can only be enqueued for batching after the first one already returned a result)

thiserror::Error,
};

pub const DEFAULT_VERIFICATION_TIMEOUT: Duration = Duration::from_secs(1);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This can be private and moved into the test module since the config defines its timeout default separately, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's used in e2e and in the balances overrides constructor (internally)
I can create a separate Detector constructor that brings this by default but I don't think it's worth it

(Yet, another argument for the "config inside configured struct crate" strategy 😛)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ahh, sorry. I checked out the branch and grepped for that identifier but the usage in e2e didn't show up for some reason. 👌

@jmg-duarte jmg-duarte added this pull request to the merge queue Apr 9, 2026
Merged via the queue into main with commit 1af5aa6 Apr 9, 2026
29 checks passed
@jmg-duarte jmg-duarte deleted the jmgd/fix/sload branch April 9, 2026 09:12
@github-actions github-actions bot locked and limited conversation to collaborators Apr 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

hotfix Labels PRs that should be applied into production right away

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants