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

perf: fetch withdrawals in parallel when possible #2147

Merged
merged 11 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 {
Copy link
Member

Choose a reason for hiding this comment

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

unused 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 }))
Comment on lines -103 to -118
Copy link
Member Author

Choose a reason for hiding this comment

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

baiscally, all this was moved to fetchWithdrawals so you can have some more control from the outside

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
Loading