-
Notifications
You must be signed in to change notification settings - Fork 203
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
Changes from all commits
4588f8c
fb8e92f
57d4a29
b153022
565ad23
edcd366
5992894
fc97519
e9c8f56
b158c14
a33a55f
92e66ec
924e0a4
e15683a
75d9823
3941f7b
6067f9c
43a9f82
aeaa9a9
33b2240
b45452b
57ef6b9
7bcda1f
82c13c1
751be46
8ec6b17
a7deab2
5cbb81c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export function wait(ms: number) { | ||
return new Promise(resolve => setTimeout(resolve, ms)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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]. | ||
* | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. extracted into type |
||
}: FetchTokenWithdrawalsFromEventLogsParams) { | ||
const erc20Bridger = await Erc20Bridger.fromProvider(l2Provider) | ||
const promises: ReturnType<Erc20Bridger['getWithdrawalEvents']>[] = [] | ||
|
||
const senderNonce = await getNonce(sender, { provider: l2Provider }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved to |
||
|
||
l2GatewayAddresses.forEach(gatewayAddress => { | ||
// funds sent by this address | ||
if (sender && senderNonce > 0) { | ||
if (sender) { | ||
promises.push( | ||
erc20Bridger.getWithdrawalEvents( | ||
l2Provider, | ||
|
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 The function can be called There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
---|---|---|
|
@@ -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 | ||
|
@@ -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 | ||
} | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 => { | ||
|
There was a problem hiding this comment.
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
fromCommonUtils
would suffice?There was a problem hiding this comment.
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.