Skip to content

Commit a22bbb8

Browse files
refactor: separate out retryable-monitor core logic (#50)
* feat: refactor core and consumer logic separately * dev: refactor almost done * dev: minor
1 parent b6bf882 commit a22bbb8

File tree

11 files changed

+709
-561
lines changed

11 files changed

+709
-561
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
Logic for fetching the deposits initiated and message delivered events from the parent chain
3+
*/
4+
5+
import { Provider } from '@ethersproject/abstract-provider'
6+
import { EventFetcher } from '@arbitrum/sdk'
7+
import { Bridge__factory } from '@arbitrum/sdk/dist/lib/abi/factories/Bridge__factory'
8+
import { L1ERC20Gateway__factory } from '@arbitrum/sdk/dist/lib/abi/factories/L1ERC20Gateway__factory'
9+
import { DepositInitiatedEvent } from '@arbitrum/sdk/dist/lib/abi/L1ERC20Gateway'
10+
11+
export const getDepositInitiatedEventData = async (
12+
parentChainGatewayAddress: string,
13+
filter: {
14+
fromBlock: number
15+
toBlock: number
16+
},
17+
parentChainProvider: Provider
18+
) => {
19+
const eventFetcher = new EventFetcher(parentChainProvider)
20+
const logs = await eventFetcher.getEvents<any, DepositInitiatedEvent>(
21+
L1ERC20Gateway__factory,
22+
(g: any) => g.filters.DepositInitiated(),
23+
{
24+
...filter,
25+
address: parentChainGatewayAddress,
26+
}
27+
)
28+
29+
return logs
30+
}
31+
32+
export const getMessageDeliveredEventData = async (
33+
parentBridgeAddress: string,
34+
filter: {
35+
fromBlock: number
36+
toBlock: number
37+
},
38+
parentChainProvider: Provider
39+
) => {
40+
const eventFetcher = new EventFetcher(parentChainProvider)
41+
const logs = await eventFetcher.getEvents(
42+
Bridge__factory,
43+
(g: any) => g.filters.MessageDelivered(),
44+
{ ...filter, address: parentBridgeAddress }
45+
)
46+
47+
// Filter logs where event.kind is equal to 9
48+
// https://github.com/OffchainLabs/nitro-contracts/blob/38a70a5e14f8b52478eb5db08e7551a82ced14fe/src/libraries/MessageTypes.sol#L9
49+
const filteredLogs = logs.filter(log => log.event.kind === 9)
50+
51+
return filteredLogs
52+
}
53+
54+
export const getDepositInitiatedLogs = async ({
55+
fromBlock,
56+
toBlock,
57+
parentChainProvider,
58+
gatewayAddresses,
59+
}: {
60+
fromBlock: number
61+
toBlock: number
62+
parentChainProvider: Provider
63+
gatewayAddresses: {
64+
parentErc20Gateway: string
65+
parentCustomGateway: string
66+
parentWethGateway: string
67+
}
68+
}) => {
69+
const [
70+
depositsInitiatedLogsL1Erc20Gateway,
71+
depositsInitiatedLogsL1CustomGateway,
72+
depositsInitiatedLogsL1WethGateway,
73+
] = await Promise.all(
74+
[
75+
gatewayAddresses.parentErc20Gateway,
76+
gatewayAddresses.parentCustomGateway,
77+
gatewayAddresses.parentWethGateway,
78+
].map(gatewayAddress => {
79+
return getDepositInitiatedEventData(
80+
gatewayAddress,
81+
{ fromBlock, toBlock },
82+
parentChainProvider
83+
)
84+
})
85+
)
86+
87+
return [
88+
...depositsInitiatedLogsL1Erc20Gateway,
89+
...depositsInitiatedLogsL1CustomGateway,
90+
...depositsInitiatedLogsL1WethGateway,
91+
]
92+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
Logic for generating the intermediate details for the Retryables
3+
*/
4+
5+
import {
6+
ParentToChildMessageStatus,
7+
ParentTransactionReceipt,
8+
ParentToChildMessageReader,
9+
} from '@arbitrum/sdk'
10+
import { BigNumber, providers } from 'ethers'
11+
import { TransactionReceipt } from '@ethersproject/abstract-provider'
12+
import { SEVEN_DAYS_IN_SECONDS } from '@arbitrum/sdk/dist/lib/dataEntities/constants'
13+
import { ChildChainTicketReport, ParentChainTicketReport } from './types'
14+
15+
export const getParentChainRetryableReport = (
16+
arbParentTxReceipt: ParentTransactionReceipt,
17+
retryableMessage: ParentToChildMessageReader
18+
): ParentChainTicketReport => {
19+
return {
20+
id: arbParentTxReceipt.transactionHash,
21+
transactionHash: arbParentTxReceipt.transactionHash,
22+
sender: arbParentTxReceipt.from,
23+
retryableTicketID: retryableMessage.retryableCreationId,
24+
}
25+
}
26+
27+
export const getChildChainRetryableReport = async ({
28+
childChainTx,
29+
childChainTxReceipt,
30+
retryableMessage,
31+
childChainProvider,
32+
}: {
33+
childChainTx: providers.TransactionResponse
34+
childChainTxReceipt: TransactionReceipt
35+
retryableMessage: ParentToChildMessageReader
36+
childChainProvider: providers.Provider
37+
}): Promise<ChildChainTicketReport> => {
38+
let status = await retryableMessage.status()
39+
40+
const timestamp = (
41+
await childChainProvider.getBlock(childChainTxReceipt.blockNumber)
42+
).timestamp
43+
44+
const childChainTicketReport = {
45+
id: retryableMessage.retryableCreationId,
46+
retryTxHash: (await retryableMessage.getAutoRedeemAttempt())
47+
?.transactionHash,
48+
createdAtTimestamp: String(timestamp),
49+
createdAtBlockNumber: childChainTxReceipt.blockNumber,
50+
timeoutTimestamp: String(Number(timestamp) + SEVEN_DAYS_IN_SECONDS),
51+
deposit: String(retryableMessage.messageData.l2CallValue), // eth amount
52+
status: ParentToChildMessageStatus[status],
53+
retryTo: retryableMessage.messageData.destAddress,
54+
retryData: retryableMessage.messageData.data,
55+
gasFeeCap: (childChainTx.maxFeePerGas ?? BigNumber.from(0)).toNumber(),
56+
gasLimit: childChainTx.gasLimit.toNumber(),
57+
}
58+
59+
return childChainTicketReport
60+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
This file contains the logic for checking retryables in the given block range for the chain
3+
*/
4+
5+
import { providers } from 'ethers'
6+
import {
7+
ParentTransactionReceipt,
8+
ParentToChildMessageStatus,
9+
} from '@arbitrum/sdk'
10+
import { ChildNetwork } from '../../utils'
11+
import {
12+
getMessageDeliveredEventData,
13+
getDepositInitiatedLogs,
14+
} from './depositEventFetcher'
15+
import {
16+
getParentChainRetryableReport,
17+
getChildChainRetryableReport,
18+
} from './reportGenerator'
19+
import { getExplorerUrlPrefixes } from '../../utils'
20+
import { OnFailedRetryableFound } from './types'
21+
import { getTokenDepositData } from './tokenDataFetcher'
22+
23+
export const checkRetryables = async (
24+
parentChainProvider: providers.Provider,
25+
childChainProvider: providers.Provider,
26+
childChain: ChildNetwork,
27+
bridgeAddress: string,
28+
fromBlock: number,
29+
toBlock: number,
30+
enableAlerting: boolean,
31+
onFailedRetryableFound?: OnFailedRetryableFound
32+
): Promise<boolean> => {
33+
let retryablesFound = false
34+
35+
const messageDeliveredLogs = await getMessageDeliveredEventData(
36+
bridgeAddress,
37+
{ fromBlock, toBlock },
38+
parentChainProvider
39+
)
40+
41+
// used for finding the token-details associated with a deposit, if any
42+
const depositsInitiatedLogs = await getDepositInitiatedLogs({
43+
fromBlock,
44+
toBlock,
45+
parentChainProvider,
46+
gatewayAddresses: {
47+
parentErc20Gateway: childChain.tokenBridge!.parentErc20Gateway,
48+
parentCustomGateway: childChain.tokenBridge!.parentCustomGateway,
49+
parentWethGateway: childChain.tokenBridge!.parentWethGateway,
50+
},
51+
})
52+
53+
const uniqueTxHashes = new Set<string>()
54+
for (let messageDeliveredLog of messageDeliveredLogs) {
55+
const { transactionHash: parentTxHash } = messageDeliveredLog
56+
uniqueTxHashes.add(parentTxHash)
57+
}
58+
59+
const { PARENT_CHAIN_TX_PREFIX, CHILD_CHAIN_TX_PREFIX } =
60+
getExplorerUrlPrefixes(childChain)
61+
62+
// for each parent-chain-transaction found, extract the Retryables thus created by it
63+
for (const parentTxHash of uniqueTxHashes) {
64+
const parentTxReceipt = await parentChainProvider.getTransactionReceipt(
65+
parentTxHash
66+
)
67+
const arbParentTxReceipt = new ParentTransactionReceipt(parentTxReceipt)
68+
const retryables = await arbParentTxReceipt.getParentToChildMessages(
69+
childChainProvider
70+
)
71+
72+
if (retryables.length > 0) {
73+
console.log(
74+
`${retryables.length} retryable${
75+
retryables.length === 1 ? '' : 's'
76+
} found for ${
77+
childChain.name
78+
} chain. Checking their status:\n\nParentChainTxHash: ${
79+
PARENT_CHAIN_TX_PREFIX + parentTxHash
80+
}`
81+
)
82+
console.log('----------------------------------------------------------')
83+
84+
// for each retryable, extract the detail for it's status / redemption
85+
for (let msgIndex = 0; msgIndex < retryables.length; msgIndex++) {
86+
const retryableMessage = retryables[msgIndex]
87+
const retryableTicketId = retryableMessage.retryableCreationId
88+
let status = await retryableMessage.status()
89+
90+
// if a Retryable is not in a successful state, extract it's details
91+
if (status !== ParentToChildMessageStatus.REDEEMED) {
92+
const childChainTx = await childChainProvider.getTransaction(
93+
retryableTicketId
94+
)
95+
const childChainTxReceipt =
96+
await childChainProvider.getTransactionReceipt(
97+
retryableMessage.retryableCreationId
98+
)
99+
100+
if (!childChainTxReceipt) {
101+
// if child-chain tx is very recent, the tx receipt might not be found yet
102+
// if not handled, this will result in `undefined` error while trying to extract retryable details
103+
console.log(
104+
`${msgIndex + 1}. ${
105+
ParentToChildMessageStatus[status]
106+
}:\nChildChainTxHash: ${
107+
CHILD_CHAIN_TX_PREFIX + retryableTicketId
108+
} (Receipt not found yet)`
109+
)
110+
continue
111+
}
112+
113+
const parentChainRetryableReport = getParentChainRetryableReport(
114+
arbParentTxReceipt,
115+
retryableMessage
116+
)
117+
const childChainRetryableReport = await getChildChainRetryableReport({
118+
retryableMessage,
119+
childChainTx,
120+
childChainTxReceipt,
121+
childChainProvider,
122+
})
123+
const tokenDepositData = await getTokenDepositData({
124+
childChainTx,
125+
retryableMessage,
126+
arbParentTxReceipt,
127+
depositsInitiatedLogs,
128+
parentChainProvider,
129+
})
130+
131+
// Call the provided callback if it exists
132+
if (enableAlerting && onFailedRetryableFound) {
133+
await onFailedRetryableFound({
134+
parentChainRetryableReport,
135+
childChainRetryableReport,
136+
tokenDepositData,
137+
childChain,
138+
})
139+
}
140+
}
141+
142+
// format the result message
143+
console.log(
144+
`${msgIndex + 1}. ${
145+
ParentToChildMessageStatus[status]
146+
}:\nChildChainTxHash: ${CHILD_CHAIN_TX_PREFIX + retryableTicketId}`
147+
)
148+
console.log(
149+
'----------------------------------------------------------'
150+
)
151+
}
152+
retryablesFound = true // Set to true if retryables are found
153+
}
154+
}
155+
156+
return retryablesFound
157+
}

0 commit comments

Comments
 (0)