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: load withdrawal tx history by priority #2128

Merged
merged 28 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 27 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function wait(ms: number) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not super sure about the filename since wait is generic, doesn't have much to do with exponential wait-time, and might be hard to discover later.
Unless we plan on adding a lot more related functions here, I guess just having wait from CommonUtils would suffice?

Copy link
Contributor

Choose a reason for hiding this comment

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

Will be taken up in next PR.

return new Promise(resolve => setTimeout(resolve, ms))
}
15 changes: 1 addition & 14 deletions packages/arb-token-bridge-ui/src/util/fetchL2Gateways.ts
Copy link
Member Author

Choose a reason for hiding this comment

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

this function will now be used only to keep track of custom custom gateways

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 @@ -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[]
}) {
Comment on lines -33 to -40
Copy link
Member Author

Choose a reason for hiding this comment

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

extracted into type FetchTokenWithdrawalsFromEventLogsParams

}: FetchTokenWithdrawalsFromEventLogsParams) {
const erc20Bridger = await Erc20Bridger.fromProvider(l2Provider)
const promises: ReturnType<Erc20Bridger['getWithdrawalEvents']>[] = []

const senderNonce = await getNonce(sender, { provider: l2Provider })
Copy link
Member Author

Choose a reason for hiding this comment

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

moved to fetchWithdrawals.ts so we do it just once


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 { 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)
Copy link
Contributor

Choose a reason for hiding this comment

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

Outside the scope of the current PR, but since it touches this code, we might want to rename fetchL2Gateways to a better generic name. Infact I would suggest we don;t need the fetchL2Gateways file anymore and we can move it inside this file only.

The function can be called fetchOtherGateways to better match it's usage.

Copy link
Contributor

Choose a reason for hiding this comment

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

Will be taken up in next PR.


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 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 =>
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 { 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,23 @@ 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 fetchETHWithdrawalsFromEventLogs({
receiver,
fromBlock: toBlock + 1,
toBlock: 'latest',
l2Provider: l2Provider
})

await wait(2_000)

const tokenWithdrawalsFromEventLogs =
Comment on lines +87 to +96
Copy link
Member Author

Choose a reason for hiding this comment

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

load eth withdrawals before token withdrawals

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

const mappedEthWithdrawalsFromEventLogs: Withdrawal[] =
ethWithdrawalsFromEventLogs.map(tx => {
Expand Down
Loading