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

feat: add exponential backoff for withdrawals queries #2133

Merged
merged 33 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4588f8c
don't fetch withdrawals if nonce is zero
spsjvc Dec 9, 2024
fb8e92f
fix comments
spsjvc Dec 9, 2024
57d4a29
rename l2Provider to provider
spsjvc Dec 9, 2024
b153022
wip
spsjvc Dec 9, 2024
565ad23
reduce diff
spsjvc Dec 9, 2024
edcd366
remove duplicate check
spsjvc Dec 9, 2024
5992894
oops
spsjvc Dec 9, 2024
fc97519
Merge branch 'master' into perf-sequential-tx-history
spsjvc Dec 9, 2024
e9c8f56
dupe check
spsjvc Dec 9, 2024
b158c14
add backOff
spsjvc Dec 9, 2024
a33a55f
clean up
spsjvc Dec 9, 2024
92e66ec
clean up
spsjvc Dec 10, 2024
924e0a4
use custom custom gateways
spsjvc Dec 10, 2024
e15683a
clean up
spsjvc Dec 10, 2024
75d9823
clean up
spsjvc Dec 10, 2024
3941f7b
clean up more
spsjvc Dec 10, 2024
6067f9c
clean up
spsjvc Dec 10, 2024
43a9f82
clean up
spsjvc Dec 10, 2024
aeaa9a9
clean up more
spsjvc Dec 10, 2024
33b2240
revert exponential back off for now
spsjvc Dec 10, 2024
b45452b
wrap up
spsjvc Dec 10, 2024
57ef6b9
increase delay
spsjvc Dec 10, 2024
7bcda1f
Merge branch 'master' into perf-sequential-tx-history
spsjvc Dec 10, 2024
82c13c1
use awaited
spsjvc Dec 10, 2024
eedf57a
install exponential-backoff
spsjvc Dec 10, 2024
058674e
add backoff
spsjvc Dec 10, 2024
5baa40b
move
spsjvc Dec 10, 2024
751be46
move file
spsjvc Dec 10, 2024
8ec6b17
fetch eth and token withdrawals sequentially
spsjvc Dec 10, 2024
5737c31
Merge branch 'perf-sequential-tx-history' into feat-exponential-backoff
spsjvc Dec 10, 2024
a68c4f9
yup
spsjvc Dec 10, 2024
b90e4d9
Merge branch 'master' into feat-exponential-backoff
spsjvc Dec 12, 2024
65ff6e7
weird
spsjvc Dec 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/arb-token-bridge-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"cheerio": "^1.0.0-rc.12",
"dayjs": "^1.11.8",
"ethers": "^5.6.0",
"exponential-backoff": "^3.1.1",
"graphql": "^16.8.1",
"lodash-es": "^4.17.21",
"next": "^14.2.12",
Expand Down
14 changes: 14 additions & 0 deletions packages/arb-token-bridge-ui/src/util/ExponentialBackoffUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { backOff as _backOff, BackoffOptions } from 'exponential-backoff'

const backoffOptions: BackoffOptions = {
startingDelay: 1_000,
timeMultiple: 1.5
}

export function backOff<T>(request: () => Promise<T>): Promise<T> {
return _backOff(request, backoffOptions)
Copy link
Member

Choose a reason for hiding this comment

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

do we want to turn on full jitter?
https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/

i think we do?

}

export function wait(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms))
}
15 changes: 1 addition & 14 deletions packages/arb-token-bridge-ui/src/util/fetchL2Gateways.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { constants } from 'ethers'
import { Provider } from '@ethersproject/providers'
import { getArbitrumNetwork } from '@arbitrum/sdk'

Expand All @@ -19,26 +18,14 @@ import {
export async function fetchL2Gateways(l2Provider: Provider) {
const l2Network = await getArbitrumNetwork(l2Provider)

if (!l2Network.tokenBridge) {
return []
}

/* configure gateway addresses for fetching withdrawals */
const { childErc20Gateway, childCustomGateway, childWethGateway } =
l2Network.tokenBridge

const gatewaysToUse = [childErc20Gateway, childCustomGateway]
const gatewaysToUse = []
const l2ArbReverseGateway = l2ArbReverseGatewayAddresses[l2Network.chainId]
const l2DaiGateway = l2DaiGatewayAddresses[l2Network.chainId]
const l2wstETHGateway = l2wstETHGatewayAddresses[l2Network.chainId]
const l2LptGateway = l2LptGatewayAddresses[l2Network.chainId]
const l2MoonGateway = l2MoonGatewayAddresses[l2Network.chainId]
const l2UsdcGateway = l2UsdcGatewayAddresses[l2Network.chainId]

// custom gas token chains will have weth gateway set to address zero
if (childWethGateway !== constants.AddressZero) {
gatewaysToUse.push(childWethGateway)
}
if (l2ArbReverseGateway) {
gatewaysToUse.push(l2ArbReverseGateway)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function fetchETHWithdrawalsFromEventLogs({
l2Provider: Provider
}) {
if (typeof receiver === 'undefined') {
return []
Copy link
Member Author

Choose a reason for hiding this comment

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

otherwise the type would be never[]

return Promise.resolve([])
}

// funds received by this address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { Provider, BlockTag } from '@ethersproject/providers'
import { Erc20Bridger, EventArgs } from '@arbitrum/sdk'
import { WithdrawalInitiatedEvent } from '@arbitrum/sdk/dist/lib/abi/L2ArbitrumGateway'

import { getNonce } from '../AddressUtils'

function dedupeEvents(
events: (EventArgs<WithdrawalInitiatedEvent> & {
txHash: string
Expand All @@ -12,6 +10,15 @@ function dedupeEvents(
return [...new Map(events.map(item => [item.txHash, item])).values()]
}

export type FetchTokenWithdrawalsFromEventLogsParams = {
sender?: string
receiver?: string
fromBlock: BlockTag
toBlock: BlockTag
l2Provider: Provider
l2GatewayAddresses?: string[]
}

/**
* Fetches initiated token withdrawals from event logs in range of [fromBlock, toBlock].
*
Expand All @@ -30,22 +37,13 @@ export async function fetchTokenWithdrawalsFromEventLogs({
toBlock,
l2Provider,
l2GatewayAddresses = []
}: {
sender?: string
receiver?: string
fromBlock: BlockTag
toBlock: BlockTag
l2Provider: Provider
l2GatewayAddresses?: string[]
}) {
}: FetchTokenWithdrawalsFromEventLogsParams) {
const erc20Bridger = await Erc20Bridger.fromProvider(l2Provider)
const promises: ReturnType<Erc20Bridger['getWithdrawalEvents']>[] = []

const senderNonce = await getNonce(sender, { provider: l2Provider })

l2GatewayAddresses.forEach(gatewayAddress => {
// funds sent by this address
if (sender && senderNonce > 0) {
if (sender) {
promises.push(
erc20Bridger.getWithdrawalEvents(
l2Provider,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { constants } from 'ethers'
import { Provider, BlockTag } from '@ethersproject/providers'
import { Erc20Bridger, getArbitrumNetwork } from '@arbitrum/sdk'

import {
fetchTokenWithdrawalsFromEventLogs,
FetchTokenWithdrawalsFromEventLogsParams
} from './fetchTokenWithdrawalsFromEventLogs'
import { getNonce } from '../AddressUtils'
import { fetchL2Gateways } from '../fetchL2Gateways'
import { backOff, wait } from '../ExponentialBackoffUtils'

async function getGateways(provider: Provider): Promise<{
standardGateway: string
wethGateway: string
customGateway: string
otherGateways: string[]
}> {
const network = await getArbitrumNetwork(provider)

const standardGateway = network.tokenBridge?.childErc20Gateway
const customGateway = network.tokenBridge?.childCustomGateway
const wethGateway = network.tokenBridge?.childWethGateway
const otherGateways = await fetchL2Gateways(provider)

return {
standardGateway: standardGateway ?? constants.AddressZero,
wethGateway: wethGateway ?? constants.AddressZero,
customGateway: customGateway ?? constants.AddressZero,
otherGateways
}
}

type TokenWithdrawalQuery = {
params: FetchTokenWithdrawalsFromEventLogsParams
priority: number
}

export type FetchTokenWithdrawalsFromEventLogsSequentiallyParams = {
sender?: string
receiver?: string
provider: Provider
fromBlock?: BlockTag
toBlock?: BlockTag
/**
* How long to delay in-between queries of different priority.
*/
delayMs?: number
}

export type FetchTokenWithdrawalsFromEventLogsSequentiallyResult = Awaited<
ReturnType<Erc20Bridger['getWithdrawalEvents']>
>

export async function fetchTokenWithdrawalsFromEventLogsSequentially({
sender,
receiver,
provider,
fromBlock = 0,
toBlock = 'latest',
delayMs = 2_000
}: FetchTokenWithdrawalsFromEventLogsSequentiallyParams): Promise<FetchTokenWithdrawalsFromEventLogsSequentiallyResult> {
// keep track of priority; increment as queries are added
let priority = 0
// keep track of queries
const queries: TokenWithdrawalQuery[] = []

// helper function to reuse common params
function buildQueryParams({
sender,
receiver,
gateways = []
}: {
sender?: string
receiver?: string
gateways?: string[]
}): TokenWithdrawalQuery['params'] {
return {
sender,
receiver,
fromBlock,
toBlock,
l2Provider: provider,
l2GatewayAddresses: gateways
}
}

// for sanitizing, adding queries and incrementing priority
function addQuery(params: TokenWithdrawalQuery['params']) {
const gateways = params.l2GatewayAddresses ?? []
const gatewaysSanitized = gateways.filter(g => g !== constants.AddressZero)

if (gatewaysSanitized.length === 0) {
return
}

queries.push({
params: { ...params, l2GatewayAddresses: gatewaysSanitized },
priority: ++priority
})
}

const gateways = await getGateways(provider)
const senderNonce = await backOff(() => getNonce(sender, { provider }))

// sender queries; only add if nonce > 0
if (senderNonce > 0) {
addQuery(buildQueryParams({ sender, gateways: [gateways.standardGateway] }))
addQuery(buildQueryParams({ sender, gateways: [gateways.wethGateway] }))
addQuery(buildQueryParams({ sender, gateways: [gateways.customGateway] }))
addQuery(buildQueryParams({ sender, gateways: gateways.otherGateways }))
}

// receiver queries
addQuery(buildQueryParams({ receiver, gateways: [gateways.standardGateway] }))
addQuery(buildQueryParams({ receiver, gateways: [gateways.wethGateway] }))
addQuery(buildQueryParams({ receiver, gateways: [gateways.customGateway] }))
addQuery(buildQueryParams({ receiver, gateways: gateways.otherGateways }))

// for iterating through all priorities in the while loop below
let currentPriority = 1

// final result
const result: FetchTokenWithdrawalsFromEventLogsSequentiallyResult = []

while (currentPriority <= priority) {
const currentPriorityQueries = queries.filter(
query => query.priority === currentPriority
)

const currentPriorityResults = await Promise.all(
currentPriorityQueries.map(query =>
backOff(() => fetchTokenWithdrawalsFromEventLogs(query.params))
)
)

currentPriorityResults.forEach(r => {
result.push(...r)
})

await wait(delayMs)

currentPriority++
}

return result
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import {
fetchWithdrawalsFromSubgraph
} from './fetchWithdrawalsFromSubgraph'
import { fetchLatestSubgraphBlockNumber } from '../SubgraphUtils'
import { fetchTokenWithdrawalsFromEventLogs } from './fetchTokenWithdrawalsFromEventLogs'
import { fetchL2Gateways } from '../fetchL2Gateways'

import { Withdrawal } from '../../hooks/useTransactionHistory'
import { attachTimestampToTokenWithdrawal } from './helpers'
import { WithdrawalInitiated } from '../../hooks/arbTokenBridge.types'
import { fetchTokenWithdrawalsFromEventLogsSequentially } from './fetchTokenWithdrawalsFromEventLogsSequentially'
import { backOff, wait } from '../ExponentialBackoffUtils'

export type FetchWithdrawalsParams = {
sender?: string
Expand Down Expand Up @@ -43,8 +44,6 @@ export async function fetchWithdrawals({
const l1ChainID = (await l1Provider.getNetwork()).chainId
const l2ChainID = (await l2Provider.getNetwork()).chainId

const l2GatewayAddresses = await fetchL2Gateways(l2Provider)

if (!fromBlock) {
fromBlock = 0
}
Expand Down Expand Up @@ -85,23 +84,25 @@ export async function fetchWithdrawals({
console.log('Error fetching withdrawals from subgraph', error)
}

const [ethWithdrawalsFromEventLogs, tokenWithdrawalsFromEventLogs] =
await Promise.all([
fetchETHWithdrawalsFromEventLogs({
receiver,
fromBlock: toBlock + 1,
toBlock: 'latest',
l2Provider: l2Provider
}),
fetchTokenWithdrawalsFromEventLogs({
sender,
receiver,
fromBlock: toBlock + 1,
toBlock: 'latest',
l2Provider: l2Provider,
l2GatewayAddresses
})
])
const ethWithdrawalsFromEventLogs = await backOff(() =>
fetchETHWithdrawalsFromEventLogs({
receiver,
fromBlock: toBlock! + 1,
toBlock: 'latest',
l2Provider: l2Provider
})
)

await wait(2_000)

const tokenWithdrawalsFromEventLogs =
await fetchTokenWithdrawalsFromEventLogsSequentially({
sender,
receiver,
fromBlock: toBlock + 1,
toBlock: 'latest',
provider: l2Provider
})

const mappedEthWithdrawalsFromEventLogs: Withdrawal[] =
ethWithdrawalsFromEventLogs.map(tx => {
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7545,6 +7545,11 @@ expect@^29.0.0, expect@^29.5.0:
jest-message-util "^29.5.0"
jest-util "^29.5.0"

exponential-backoff@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6"
integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==

express@^4.17.3:
version "4.21.0"
resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915"
Expand Down
Loading