Skip to content

Commit

Permalink
Split worker code for watching evm withdrawals
Browse files Browse the repository at this point in the history
  • Loading branch information
gndelia committed Jan 21, 2025
1 parent ece997a commit 4be48d7
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 137 deletions.
104 changes: 104 additions & 0 deletions webapp/utils/watch/evmWithdrawals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import pMemoize from 'promise-mem'
import { ToEvmWithdrawOperation } from 'types/tunnel'
import { findChainById } from 'utils/chain'
import { createQueuedCrossChainMessenger } from 'utils/crossChainMessenger'
import { getEvmBlock, getEvmTransactionReceipt } from 'utils/evmApi'
import { createPublicProvider } from 'utils/providers'
import { Chain } from 'viem'

// Memoized cross chain messenger as this will be created by many withdrawals
const getCrossChainMessenger = pMemoize(
function (l1Chain: Chain, l2Chain: Chain) {
const l1Provider = createPublicProvider(
l1Chain.rpcUrls.default.http[0],
l1Chain,
)

const l2Provider = createPublicProvider(
l2Chain.rpcUrls.default.http[0],
l2Chain,
)

return createQueuedCrossChainMessenger({
l1ChainId: l1Chain.id,
l1Signer: l1Provider,
l2Chain,
l2Signer: l2Provider,
})
},
{ resolver: (l1Chain, l2Chain) => `${l1Chain.id}-${l2Chain.id}` },
)

const getTransactionBlockNumber = function (
withdrawal: ToEvmWithdrawOperation,
) {
if (withdrawal.blockNumber) {
return Promise.resolve(withdrawal.blockNumber)
}
return getEvmTransactionReceipt(
withdrawal.transactionHash,
withdrawal.l2ChainId,
).then(transactionReceipt =>
// return undefined if TX is not found - might have not been confirmed yet
transactionReceipt ? Number(transactionReceipt.blockNumber) : undefined,
)
}

const getBlockTimestamp = (withdrawal: ToEvmWithdrawOperation) =>
async function (
blockNumber: number | undefined,
): Promise<[number?, number?]> {
// Can't return a block if we don't know the number
if (blockNumber === undefined) {
return []
}
// Block and timestamp already known - return them
if (withdrawal.timestamp) {
return [blockNumber, withdrawal.timestamp]
}
const { timestamp } = await getEvmBlock(blockNumber, withdrawal.l2ChainId)
return [blockNumber, Number(timestamp)]
}

export const watchEvmWithdrawal = async function (
withdrawal: ToEvmWithdrawOperation,
) {
const updates: Partial<ToEvmWithdrawOperation> = {}

// as this worker watches withdrawals to EVM chains, l1Chain will be (EVM) Chain
const l1Chain = findChainById(withdrawal.l1ChainId) as Chain
// L2 are always EVM
const l2Chain = findChainById(withdrawal.l2ChainId) as Chain

const crossChainMessenger = await getCrossChainMessenger(l1Chain, l2Chain)
const receipt = await getEvmTransactionReceipt(
withdrawal.transactionHash,
withdrawal.l2ChainId,
)

if (!receipt) {
return updates
}

const [status, [blockNumber, timestamp]] = await Promise.all([
crossChainMessenger.getMessageStatus(
withdrawal.transactionHash,
// default value, but we want to set direction
0,
withdrawal.direction,
),
getTransactionBlockNumber(withdrawal).then(getBlockTimestamp(withdrawal)),
])

if (withdrawal.status !== status) {
updates.status = status
}
if (withdrawal.blockNumber !== blockNumber) {
updates.blockNumber = blockNumber
}
if (withdrawal.timestamp !== timestamp) {
updates.timestamp = timestamp
}

return updates
}
150 changes: 13 additions & 137 deletions webapp/workers/watchEvmWithdrawals.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { MessageStatus } from '@eth-optimism/sdk'
import debugConstructor from 'debug'
import PQueue from 'p-queue'
import pMemoize from 'promise-mem'
import { RemoteChain } from 'types/chain'
import {
type ToEvmWithdrawOperation,
type WithdrawTunnelOperation,
} from 'types/tunnel'
import { findChainById } from 'utils/chain'
import { createQueuedCrossChainMessenger } from 'utils/crossChainMessenger'
import { getEvmBlock, getEvmTransactionReceipt } from 'utils/evmApi'
import { createPublicProvider } from 'utils/providers'
import { type EnableWorkersDebug } from 'utils/typeUtilities'
import { hasKeys } from 'utils/utilities'
import { type Chain, type Hash } from 'viem'
import { watchEvmWithdrawal } from 'utils/watch/evmWithdrawals'
import { type Hash } from 'viem'

const queue = new PQueue({ concurrency: 2 })

Expand Down Expand Up @@ -62,145 +58,25 @@ const getPriority = function (withdrawal: ToEvmWithdrawOperation) {
return 0
}

const getBlockTimestamp = (withdrawal: ToEvmWithdrawOperation) =>
async function (
blockNumber: number | undefined,
): Promise<[number?, number?]> {
// Can't return a block if we don't know the number
if (blockNumber === undefined) {
return []
const postUpdates = (withdrawal: ToEvmWithdrawOperation) =>
function (updates: Partial<ToEvmWithdrawOperation> = {}) {
if (hasKeys(updates)) {
debug('Sending changes for withdrawal %s', withdrawal.transactionHash)
} else {
debug('No changes for withdrawal %s', withdrawal.transactionHash)
}
// Block and timestamp already known - return them
if (withdrawal.timestamp) {
return [blockNumber, withdrawal.timestamp]
}
const { timestamp } = await getEvmBlock(blockNumber, withdrawal.l2ChainId)
return [blockNumber, Number(timestamp)]
}

const getTransactionBlockNumber = function (
withdrawal: ToEvmWithdrawOperation,
) {
if (withdrawal.blockNumber) {
return Promise.resolve(withdrawal.blockNumber)
}
return getEvmTransactionReceipt(
withdrawal.transactionHash,
withdrawal.l2ChainId,
).then(transactionReceipt =>
// return undefined if TX is not found - might have not been confirmed yet
transactionReceipt ? Number(transactionReceipt.blockNumber) : undefined,
)
}

// Memoized cross chain messenger as this will be created by many withdrawals
const getCrossChainMessenger = pMemoize(
function (l1Chain: Chain, l2Chain: Chain) {
const l1Provider = createPublicProvider(
l1Chain.rpcUrls.default.http[0],
l1Chain,
)

const l2Provider = createPublicProvider(
l2Chain.rpcUrls.default.http[0],
l2Chain,
)

debug(
'Creating cross chain messenger for L1 %s and L2 %s',
l1Chain.id,
l2Chain.id,
)

return createQueuedCrossChainMessenger({
l1ChainId: l1Chain.id,
l1Signer: l1Provider,
l2Chain,
l2Signer: l2Provider,
worker.postMessage({
type: getUpdateWithdrawalKey(withdrawal),
updates,
})
},
{ resolver: (l1Chain, l2Chain) => `${l1Chain.id}-${l2Chain.id}` },
)
}

const watchWithdrawal = (withdrawal: ToEvmWithdrawOperation) =>
// Use a queue to avoid firing lots of requests. Throttling may also not work because it throttles
// for a specific period of time and depending on load, requests may take up to 5 seconds to complete
// so this let us to query up to <concurrency> checks for status at the same time
queue.add(
async function checkWithdrawalUpdates() {
// as this worker watches withdrawals to EVM chains, l1Chain will be (EVM) Chain
const l1Chain = findChainById(withdrawal.l1ChainId) as Chain
// L2 are always EVM
const l2Chain = findChainById(withdrawal.l2ChainId) as Chain

const crossChainMessenger = await getCrossChainMessenger(l1Chain, l2Chain)
debug('Checking withdrawal %s', withdrawal.transactionHash)

const updates: Partial<ToEvmWithdrawOperation> = {}

const receipt = await getEvmTransactionReceipt(
withdrawal.transactionHash,
withdrawal.l2ChainId,
)

if (!receipt) {
debug('Withdrawal %s is not confirmed yet', withdrawal.transactionHash)
worker.postMessage({
type: getUpdateWithdrawalKey(withdrawal),
updates,
})
return
}

const [status, [blockNumber, timestamp]] = await Promise.all([
crossChainMessenger.getMessageStatus(
withdrawal.transactionHash,
// default value, but we want to set direction
0,
withdrawal.direction,
),
getTransactionBlockNumber(withdrawal).then(
getBlockTimestamp(withdrawal),
),
])

if (withdrawal.status !== status) {
debug(
'Withdrawal %s status changed from %s to %s',
withdrawal.transactionHash,
withdrawal.status ?? 'none',
status,
)
updates.status = status
}
if (withdrawal.blockNumber !== blockNumber) {
debug(
'Saving block number %s for withdrawal %s',
blockNumber,
withdrawal.transactionHash,
)
updates.blockNumber = blockNumber
}
if (withdrawal.timestamp !== timestamp) {
debug(
'Saving timestamp %s for withdrawal %s',
timestamp,
withdrawal.transactionHash,
)
updates.timestamp = timestamp
}

if (hasKeys(updates)) {
debug('Sending changes for withdrawal %s', withdrawal.transactionHash)
} else {
debug('No changes for withdrawal %s', withdrawal.transactionHash)
}

worker.postMessage({
type: getUpdateWithdrawalKey(withdrawal),
updates,
})
},
() => watchEvmWithdrawal(withdrawal).then(postUpdates(withdrawal)),
{
// Give more priority to those that require polling and are not ready or are missing information
// because if ready, after the operation they will change their status automatically and will have
Expand Down

0 comments on commit 4be48d7

Please sign in to comment.