Skip to content

Commit 74d1117

Browse files
codybornclaude
andcommitted
refactor: chain-agnostic MIN_RETRY_WAIT_SECONDS for Step Functions retries
Rename MIN_RETRY_WAIT_SECONDS_TEMPO to MIN_RETRY_WAIT_SECONDS, drop the Tempo-only branch in calculateDutchRetryWaitSeconds, and apply the floor to all chains. The floor exists to prevent Step Functions Wait state from rounding sub-second block times to zero — that risk applies to any future chain with sub-second blocks, not just Tempo. Lowered the floor value from 2s to 1s (the minimum Step Functions can represent). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent afe5d4f commit 74d1117

2 files changed

Lines changed: 20 additions & 35 deletions

File tree

lib/handlers/check-order-status/util.ts

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,10 @@ export const AVERAGE_BLOCK_TIME = (chainId: ChainId): number => {
274274
case ChainId.CELO:
275275
return 5
276276
case ChainId.TEMPO:
277-
// Tempo blocks are ~500ms. Reported in seconds as a fractional value
278-
// for accurate block-number arithmetic (see timestampToBlockNumber).
279-
// Step Functions retry waits floor this via MIN_RETRY_WAIT_SECONDS_TEMPO
280-
// in calculateDutchRetryWaitSeconds.
277+
// Reported in seconds as a fractional value for accurate block-number
278+
// arithmetic (see timestampToBlockNumber); calculateDutchRetryWaitSeconds
279+
// floors retry waits at MIN_RETRY_WAIT_SECONDS so Step Functions Wait
280+
// state doesn't round to zero.
281281
return 0.5
282282
case ChainId.POLYGON:
283283
// Keep this at the default 12 for now since we would have to do more retries
@@ -355,21 +355,12 @@ export function getValidator(provider: ethers.providers.StaticJsonRpcProvider, c
355355
}
356356

357357
/*
358-
* Minimum wait between Step Functions retries on Tempo.
359-
*
360-
* Step Functions Wait state granularity is whole seconds, and Tempo's ~500ms
361-
* block time (AVERAGE_BLOCK_TIME = 0.5) would round down to 0 — effectively a
362-
* hot loop. Even at 1s per retry, 300 first-hour retries with ~7 history
363-
* events each would chew through the 25,000-event execution-history limit.
364-
*
365-
* 2 seconds keeps the first-hour-polling phase well under the event limit
366-
* (300 retries * ~7 events ≈ 2,100) while still being responsive enough to
367-
* detect fills promptly on Tempo.
368-
*
369-
* This floor is intentionally scoped to Tempo only so we don't change
370-
* behavior on chains like Arbitrum/Unichain where 1-second waits are fine.
358+
* Minimum wait between Step Functions retries. Wait state granularity is
359+
* whole seconds, so any sub-second block time would round to zero and
360+
* hot-loop the state machine. Floor is one second — Step Functions' minimum
361+
* representable wait.
371362
*/
372-
export const MIN_RETRY_WAIT_SECONDS_TEMPO = 2
363+
export const MIN_RETRY_WAIT_SECONDS = 1
373364

374365
/*
375366
* In the first hour of order submission, we check the order status roughly every block.
@@ -383,8 +374,5 @@ export function calculateDutchRetryWaitSeconds(chainId: ChainId, retryCount: num
383374
: retryCount <= 450
384375
? Math.ceil(AVERAGE_BLOCK_TIME(chainId) * Math.pow(1.05, retryCount - 300))
385376
: 18000
386-
if (chainId === ChainId.TEMPO) {
387-
return Math.max(MIN_RETRY_WAIT_SECONDS_TEMPO, calculated)
388-
}
389-
return calculated
377+
return Math.max(MIN_RETRY_WAIT_SECONDS, calculated)
390378
}

test/unit/services/check-order-status/util.test.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ethers } from 'ethers'
22
import {
33
AVERAGE_BLOCK_TIME,
44
calculateDutchRetryWaitSeconds,
5-
MIN_RETRY_WAIT_SECONDS_TEMPO,
5+
MIN_RETRY_WAIT_SECONDS,
66
timestampToBlockNumber,
77
} from '../../../../lib/handlers/check-order-status/util'
88
import { ChainId } from '../../../../lib/util/chain'
@@ -24,34 +24,31 @@ describe('calculateDutchRetryWaitSeconds', () => {
2424
expect(response).toEqual(18000)
2525
})
2626

27-
describe('Tempo floor', () => {
28-
it('floors first-hour polling waits at MIN_RETRY_WAIT_SECONDS_TEMPO (Tempo block time = 0.5s)', () => {
29-
// Without the floor, AVERAGE_BLOCK_TIME(TEMPO) = 0.5 would round down
30-
// to 0 in Step Functions Wait granularity.
27+
describe('sub-second-block floor', () => {
28+
it(`floors sub-second block-time chains at MIN_RETRY_WAIT_SECONDS (${1}s)`, () => {
29+
// AVERAGE_BLOCK_TIME(TEMPO) = 0.5 would round down to 0 in Step
30+
// Functions Wait granularity without the floor.
3131
for (const retryCount of [1, 50, 150, 299, 300]) {
32-
const response = calculateDutchRetryWaitSeconds(ChainId.TEMPO, retryCount)
33-
expect(response).toBeGreaterThanOrEqual(MIN_RETRY_WAIT_SECONDS_TEMPO)
32+
expect(calculateDutchRetryWaitSeconds(ChainId.TEMPO, retryCount)).toEqual(MIN_RETRY_WAIT_SECONDS)
3433
}
3534
})
3635

3736
it('keeps the floor across the exponential backoff range', () => {
3837
for (const retryCount of [301, 350, 400, 450, 500]) {
3938
const response = calculateDutchRetryWaitSeconds(ChainId.TEMPO, retryCount)
40-
expect(response).toBeGreaterThanOrEqual(MIN_RETRY_WAIT_SECONDS_TEMPO)
39+
expect(response).toBeGreaterThanOrEqual(MIN_RETRY_WAIT_SECONDS)
4140
}
4241
})
4342
})
4443

45-
describe('non-Tempo chains are unaffected by the Tempo floor', () => {
46-
// The Tempo floor must NOT change behavior for chains whose AVERAGE_BLOCK_TIME
47-
// is already >= 1s. These expectations match the pre-floor base math.
48-
it('returns AVERAGE_BLOCK_TIME for Arbitrum during the first-hour polling phase (1s, not 2s)', () => {
44+
describe('chains with AVERAGE_BLOCK_TIME >= floor are unaffected', () => {
45+
it('returns AVERAGE_BLOCK_TIME for Arbitrum during the first-hour polling phase (1s)', () => {
4946
for (const retryCount of [1, 50, 150, 299, 300]) {
5047
expect(calculateDutchRetryWaitSeconds(ChainId.ARBITRUM_ONE, retryCount)).toEqual(1)
5148
}
5249
})
5350

54-
it('returns AVERAGE_BLOCK_TIME for Unichain during the first-hour polling phase (1s, not 2s)', () => {
51+
it('returns AVERAGE_BLOCK_TIME for Unichain during the first-hour polling phase (1s)', () => {
5552
for (const retryCount of [1, 50, 150, 299, 300]) {
5653
expect(calculateDutchRetryWaitSeconds(ChainId.UNICHAIN, retryCount)).toEqual(1)
5754
}

0 commit comments

Comments
 (0)