Skip to content

Commit

Permalink
perf: fetch withdrawals in parallel when possible (#2147)
Browse files Browse the repository at this point in the history
  • Loading branch information
spsjvc authored Dec 19, 2024
1 parent b569ef1 commit 28c9287
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 56 deletions.
13 changes: 12 additions & 1 deletion packages/arb-token-bridge-ui/src/util/networks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StaticJsonRpcProvider } from '@ethersproject/providers'
import { Provider, StaticJsonRpcProvider } from '@ethersproject/providers'
import {
ArbitrumNetwork,
getChildrenForNetwork,
Expand All @@ -11,6 +11,7 @@ import { loadEnvironmentVariableWithFallback } from './index'
import { getBridgeUiConfigForChain } from './bridgeUiConfig'
import { chainIdToInfuraUrl } from './infura'
import { fetchErc20Data } from './TokenUtils'
import { orbitChains } from './orbitChainsList'

export enum ChainId {
// L1
Expand Down Expand Up @@ -581,6 +582,16 @@ export function getSupportedChainIds({
})
}

export function isAlchemyChain(chainId: number) {
const chain = orbitChains[chainId]

if (typeof chain === 'undefined') {
return false
}

return chain.rpcUrl.toLowerCase().includes('alchemy.com')
}

export function mapCustomChainToNetworkData(chain: ChainWithRpcUrl) {
// custom chain details need to be added to various objects to make it work with the UI
//
Expand Down
Original file line number Diff line number Diff line change
@@ -1,80 +1,61 @@
import { constants } from 'ethers'
import { Provider, BlockTag } from '@ethersproject/providers'
import { Erc20Bridger, getArbitrumNetwork } from '@arbitrum/sdk'
import { Erc20Bridger } 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
}
}
import { backOff, wait } from '../ExponentialBackoffUtils'

type TokenWithdrawalQuery = {
type FetchTokenWithdrawalsFromEventLogsQuery = {
params: FetchTokenWithdrawalsFromEventLogsParams
priority: number
}

export type Query = {
sender?: string
receiver?: string
gateways?: string[]
}

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

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

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

// keep track of queries
const queries: TokenWithdrawalQuery[] = []
const queries: FetchTokenWithdrawalsFromEventLogsQuery[] = []

// helper function to reuse common params
function buildQueryParams({
sender,
receiver,
gateways = []
}: {
sender?: string
receiver?: string
gateways?: string[]
}): TokenWithdrawalQuery['params'] {
}: Query): FetchTokenWithdrawalsFromEventLogsQuery['params'] {
return {
sender,
receiver,
Expand All @@ -86,7 +67,7 @@ export async function fetchTokenWithdrawalsFromEventLogsSequentially({
}

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

Expand All @@ -100,22 +81,9 @@ export async function fetchTokenWithdrawalsFromEventLogsSequentially({
})
}

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 }))
queriesProp.forEach(query => {
addQuery(buildQueryParams(query))
})

// for iterating through all priorities in the while loop below
let currentPriority = 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,37 @@ import { fetchLatestSubgraphBlockNumber } from '../SubgraphUtils'
import { Withdrawal } from '../../hooks/useTransactionHistory'
import { attachTimestampToTokenWithdrawal } from './helpers'
import { WithdrawalInitiated } from '../../hooks/arbTokenBridge.types'
import { fetchTokenWithdrawalsFromEventLogsSequentially } from './fetchTokenWithdrawalsFromEventLogsSequentially'
import {
Query,
fetchTokenWithdrawalsFromEventLogsSequentially
} from './fetchTokenWithdrawalsFromEventLogsSequentially'
import { backOff, wait } from '../ExponentialBackoffUtils'
import { isAlchemyChain } from '../networks'
import { getArbitrumNetwork } from '@arbitrum/sdk'
import { fetchL2Gateways } from '../fetchL2Gateways'
import { constants } from 'ethers'
import { getNonce } from '../AddressUtils'

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
}
}

export type FetchWithdrawalsParams = {
sender?: string
Expand Down Expand Up @@ -84,6 +113,47 @@ export async function fetchWithdrawals({
console.log('Error fetching withdrawals from subgraph', error)
}

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

const queries: Query[] = []

// alchemy as a raas has a global rate limit across their chains, so we have to fetch sequentially and wait in-between requests to work around this
const isAlchemy = isAlchemyChain(l2ChainID)
const delayMs = isAlchemy ? 2_000 : 0

const allGateways = [
gateways.standardGateway,
gateways.wethGateway,
gateways.customGateway,
...gateways.otherGateways
]

// sender queries; only add if nonce > 0
if (senderNonce > 0) {
if (isAlchemy) {
// for alchemy, fetch sequentially
queries.push({ sender, gateways: [gateways.standardGateway] })
queries.push({ sender, gateways: [gateways.wethGateway] })
queries.push({ sender, gateways: [gateways.customGateway] })
queries.push({ sender, gateways: gateways.otherGateways })
} else {
// for other chains, fetch in parallel
queries.push({ sender, gateways: allGateways })
}
}

if (isAlchemy) {
// for alchemy, fetch sequentially
queries.push({ receiver, gateways: [gateways.standardGateway] })
queries.push({ receiver, gateways: [gateways.wethGateway] })
queries.push({ receiver, gateways: [gateways.customGateway] })
queries.push({ receiver, gateways: gateways.otherGateways })
} else {
// for other chains, fetch in parallel
queries.push({ receiver, gateways: allGateways })
}

const ethWithdrawalsFromEventLogs = await backOff(() =>
fetchETHWithdrawalsFromEventLogs({
receiver,
Expand All @@ -95,15 +165,16 @@ export async function fetchWithdrawals({
})
)

await wait(2_000)
await wait(delayMs)

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

const mappedEthWithdrawalsFromEventLogs: Withdrawal[] =
Expand Down

0 comments on commit 28c9287

Please sign in to comment.