1
1
import { useQuery } from '@tanstack/react-query' ;
2
- import { providers } from 'ethers' ;
2
+ import { BigNumber , constants , ethers , providers } from 'ethers' ;
3
3
4
4
import { Mailbox__factory } from '@hyperlane-xyz/core' ;
5
5
import { utils } from '@hyperlane-xyz/utils' ;
6
6
7
7
import { getMultiProvider , getProvider } from '../../../multiProvider' ;
8
8
import { useStore } from '../../../store' ;
9
- import { Message , MessageStatus } from '../../../types' ;
9
+ import { Message , MessageStatus , PartialTransactionReceipt } from '../../../types' ;
10
10
import {
11
11
ensureLeading0x ,
12
12
isValidAddressFast ,
@@ -22,11 +22,13 @@ import { ChainConfig } from '../../chains/chainConfig';
22
22
23
23
import { isValidSearchQuery } from './useMessageQuery' ;
24
24
25
+ const PROVIDER_LOGS_BLOCK_WINDOW = 150_000 ;
26
+
25
27
const mailbox = Mailbox__factory . createInterface ( ) ;
26
28
const dispatchTopic0 = mailbox . getEventTopic ( 'Dispatch' ) ;
27
29
const dispatchIdTopic0 = mailbox . getEventTopic ( 'DispatchId' ) ;
28
- const processTopic0 = mailbox . getEventTopic ( 'Process' ) ;
29
- const processIdTopic0 = mailbox . getEventTopic ( 'ProcessId' ) ;
30
+ // const processTopic0 = mailbox.getEventTopic('Process');
31
+ // const processIdTopic0 = mailbox.getEventTopic('ProcessId');
30
32
31
33
// Query 'Permissionless Interoperability (PI)' chains using custom
32
34
// chain configs in store state
@@ -85,50 +87,50 @@ searchForMessages(input):
85
87
GOTO hash search above
86
88
*/
87
89
88
- async function fetchMessagesFromPiChain (
90
+ export async function fetchMessagesFromPiChain (
89
91
chainConfig : ChainConfig ,
90
92
input : string ,
91
93
) : Promise < Message [ ] > {
92
94
const { chainId, blockExplorers } = chainConfig ;
93
95
const useExplorer = ! ! blockExplorers ?. [ 0 ] ?. apiUrl ;
96
+ const formattedInput = ensureLeading0x ( input ) ;
94
97
95
- let logs : providers . Log [ ] | null = null ;
96
- if ( isValidAddressFast ( input ) ) {
97
- logs = await fetchLogsForAddress ( chainConfig , input , useExplorer ) ;
98
+ let logs : providers . Log [ ] ;
99
+ if ( isValidAddressFast ( formattedInput ) ) {
100
+ logs = await fetchLogsForAddress ( chainConfig , formattedInput , useExplorer ) ;
98
101
} else if ( isValidTransactionHash ( input ) ) {
99
- logs = await fetchLogsForTxHash ( chainConfig , input , useExplorer ) ;
100
- if ( ! logs ) {
102
+ logs = await fetchLogsForTxHash ( chainConfig , formattedInput , useExplorer ) ;
103
+ if ( ! logs . length ) {
101
104
// Input may be a msg id
102
- logs = await fetchLogsForMsgId ( chainConfig , input , useExplorer ) ;
105
+ logs = await fetchLogsForMsgId ( chainConfig , formattedInput , useExplorer ) ;
103
106
}
104
107
} else {
105
108
throw new Error ( 'Invalid PI search input' ) ;
106
109
}
107
110
108
- if ( ! logs ? .length ) {
111
+ if ( ! logs . length ) {
109
112
// Throw so Promise.any caller doesn't trigger
110
113
throw new Error ( `No messages found for chain ${ chainId } ` ) ;
111
114
}
112
115
113
- return logs . map ( logToMessage ) ;
116
+ return logs . map ( logToMessage ) . filter ( ( m ) : m is Message => ! ! m ) ;
114
117
}
115
118
116
119
async function fetchLogsForAddress (
117
120
{ chainId, contracts } : ChainConfig ,
118
121
address : Address ,
119
122
useExplorer ?: boolean ,
120
123
) {
124
+ logger . debug ( `Fetching logs for address ${ address } on chain ${ chainId } ` ) ;
121
125
const mailboxAddr = contracts . mailbox ;
122
- const dispatchTopic1 = ensureLeading0x ( address ) ;
123
- const dispatchTopic3 = utils . addressToBytes32 ( dispatchTopic1 ) ;
124
- const processTopic1 = dispatchTopic3 ;
125
- const processTopic3 = dispatchTopic1 ;
126
+ const dispatchTopic1 = utils . addressToBytes32 ( address ) ;
127
+ const dispatchTopic3 = dispatchTopic1 ;
126
128
127
129
if ( useExplorer ) {
128
130
return fetchLogsFromExplorer (
129
131
[
130
132
`&topic0=${ dispatchTopic0 } &topic0_1_opr=and&topic1=${ dispatchTopic1 } &topic1_3_opr=or&topic3=${ dispatchTopic3 } ` ,
131
- `&topic0=${ processTopic0 } &topic0_1_opr=and&topic1=${ processTopic1 } &topic1_3_opr=or&topic3=${ processTopic3 } ` ,
133
+ // `&topic0=${processTopic0}&topic0_1_opr=and&topic1=${dispatchTopic3 }&topic1_3_opr=or&topic3=${dispatchTopic1 }`,
132
134
] ,
133
135
mailboxAddr ,
134
136
chainId ,
@@ -138,8 +140,8 @@ async function fetchLogsForAddress(
138
140
[
139
141
[ dispatchTopic0 , dispatchTopic1 ] ,
140
142
[ dispatchTopic0 , null , null , dispatchTopic3 ] ,
141
- [ processTopic0 , processTopic1 ] ,
142
- [ processTopic0 , null , null , processTopic3 ] ,
143
+ // [processTopic0, dispatchTopic3 ],
144
+ // [processTopic0, null, null, dispatchTopic1 ],
143
145
] ,
144
146
mailboxAddr ,
145
147
chainId ,
@@ -148,55 +150,56 @@ async function fetchLogsForAddress(
148
150
}
149
151
150
152
async function fetchLogsForTxHash ( { chainId } : ChainConfig , txHash : string , useExplorer : boolean ) {
153
+ logger . debug ( `Fetching logs for txHash ${ txHash } on chain ${ chainId } ` ) ;
151
154
if ( useExplorer ) {
152
155
try {
153
- const txReceipt = await queryExplorerForTxReceipt ( chainId , txHash ) ;
156
+ const txReceipt = await queryExplorerForTxReceipt ( chainId , txHash , false ) ;
154
157
logger . debug ( `Tx receipt found from explorer for chain ${ chainId } ` ) ;
155
- console . log ( txReceipt ) ;
156
158
return txReceipt . logs ;
157
159
} catch ( error ) {
158
160
logger . debug ( `Tx hash not found in explorer for chain ${ chainId } ` ) ;
159
- return null ;
160
161
}
161
162
} else {
162
163
const provider = getProvider ( chainId ) ;
163
164
const txReceipt = await provider . getTransactionReceipt ( txHash ) ;
164
- console . log ( txReceipt ) ;
165
165
if ( txReceipt ) {
166
166
logger . debug ( `Tx receipt found from provider for chain ${ chainId } ` ) ;
167
167
return txReceipt . logs ;
168
168
} else {
169
169
logger . debug ( `Tx hash not found from provider for chain ${ chainId } ` ) ;
170
- return null ;
171
170
}
172
171
}
172
+ return [ ] ;
173
173
}
174
174
175
175
async function fetchLogsForMsgId ( chainConfig : ChainConfig , msgId : string , useExplorer : boolean ) {
176
176
const { contracts, chainId } = chainConfig ;
177
+ logger . debug ( `Fetching logs for msgId ${ msgId } on chain ${ chainId } ` ) ;
177
178
const mailboxAddr = contracts . mailbox ;
178
- const topic1 = ensureLeading0x ( msgId ) ;
179
+ const topic1 = msgId ;
179
180
let logs : providers . Log [ ] ;
180
181
if ( useExplorer ) {
181
182
logs = await fetchLogsFromExplorer (
182
183
[
183
184
`&topic0=${ dispatchIdTopic0 } &topic0_1_opr=and&topic1=${ topic1 } ` ,
184
- `&topic0=${ processIdTopic0 } &topic0_1_opr=and&topic1=${ topic1 } ` ,
185
+ // `&topic0=${processIdTopic0}&topic0_1_opr=and&topic1=${topic1}`,
185
186
] ,
186
187
mailboxAddr ,
187
188
chainId ,
188
189
) ;
189
190
} else {
190
191
logs = await fetchLogsFromProvider (
191
192
[
192
- [ dispatchTopic0 , topic1 ] ,
193
- [ processTopic0 , topic1 ] ,
193
+ [ dispatchIdTopic0 , topic1 ] ,
194
+ // [processIdTopic0 , topic1],
194
195
] ,
195
196
mailboxAddr ,
196
197
chainId ,
197
198
) ;
198
199
}
199
200
201
+ // Grab first tx hash found in any log and get all logs for that tx
202
+ // Necessary because DispatchId/ProcessId logs don't contain useful info
200
203
if ( logs . length ) {
201
204
const txHash = logs [ 0 ] . transactionHash ;
202
205
logger . debug ( 'Found tx hash with log of msg id' , txHash ) ;
@@ -207,13 +210,13 @@ async function fetchLogsForMsgId(chainConfig: ChainConfig, msgId: string, useExp
207
210
}
208
211
209
212
async function fetchLogsFromExplorer ( paths : Array < string > , contractAddr : Address , chainId : number ) {
210
- const pathBase = `api ?module=logs&action=getLogs&fromBlock=0&toBlock=999999999&address=${ contractAddr } ` ;
211
- const logs = (
212
- await Promise . all ( paths . map ( ( p ) => queryExplorerForLogs ( chainId , ` ${ pathBase } ${ p } ` ) ) )
213
- )
214
- . flat ( )
215
- . map ( toProviderLog ) ;
216
- console . log ( logs ) ;
213
+ const base = `?module=logs&action=getLogs&fromBlock=0&toBlock=999999999&address=${ contractAddr } ` ;
214
+ let logs : providers . Log [ ] = [ ] ;
215
+ for ( const path of paths ) {
216
+ // Originally use parallel requests here with Promise.all but immediately hit rate limit errors
217
+ const result = await queryExplorerForLogs ( chainId , ` ${ base } ${ path } ` , undefined , false ) ;
218
+ logs = [ ... logs , ... result . map ( toProviderLog ) ] ;
219
+ }
217
220
return logs ;
218
221
}
219
222
@@ -223,46 +226,68 @@ async function fetchLogsFromProvider(
223
226
chainId : number ,
224
227
) {
225
228
const provider = getProvider ( chainId ) ;
229
+ const latestBlock = await provider . getBlockNumber ( ) ;
226
230
// TODO may need chunking here to avoid RPC errors
227
231
const logs = (
228
232
await Promise . all (
229
233
topics . map ( ( t ) =>
230
234
provider . getLogs ( {
235
+ fromBlock : latestBlock - PROVIDER_LOGS_BLOCK_WINDOW ,
236
+ toBlock : 'latest' ,
231
237
address : contractAddr ,
232
238
topics : t ,
233
239
} ) ,
234
240
) ,
235
241
)
236
242
) . flat ( ) ;
237
- console . log ( logs ) ;
238
243
return logs ;
239
244
}
240
245
241
- function logToMessage ( log : providers . Log ) : Message {
246
+ function logToMessage ( log : providers . Log ) : Message | null {
247
+ let logDesc : ethers . utils . LogDescription ;
248
+ try {
249
+ logDesc = mailbox . parseLog ( log ) ;
250
+ if ( logDesc . name . toLowerCase ( ) !== 'dispatch' ) return null ;
251
+ } catch ( error ) {
252
+ // Probably not a message log, ignore
253
+ return null ;
254
+ }
255
+
256
+ const bytes = logDesc . args [ 'message' ] ;
257
+ const message = utils . parseMessage ( bytes ) ;
258
+
259
+ const tx : PartialTransactionReceipt = {
260
+ from : constants . AddressZero , //TODO
261
+ transactionHash : log . transactionHash ,
262
+ blockNumber : BigNumber . from ( log . blockNumber ) . toNumber ( ) ,
263
+ gasUsed : 0 , //TODO
264
+ timestamp : 0 , // TODO
265
+ } ;
266
+ const emptyTx = {
267
+ from : constants . AddressZero , //TODO
268
+ transactionHash : constants . HashZero ,
269
+ blockNumber : 0 ,
270
+ gasUsed : 0 ,
271
+ timestamp : 0 ,
272
+ } ;
273
+
242
274
const multiProvider = getMultiProvider ( ) ;
243
- const bytes = mailbox . parseLog ( log ) . args [ 'message' ] ;
244
- const parsed = utils . parseMessage ( bytes ) ;
275
+
245
276
return {
246
277
id : '' , // No db id exists
247
278
msgId : utils . messageId ( bytes ) ,
248
- status : MessageStatus . Pending , // TODO
249
- sender : parsed . sender ,
250
- recipient : parsed . recipient ,
251
- originDomainId : parsed . origin ,
252
- destinationDomainId : parsed . destination ,
253
- originChainId : multiProvider . getChainId ( parsed . origin ) ,
254
- destinationChainId : multiProvider . getChainId ( parsed . destination ) ,
255
- originTimestamp : 0 , // TODO
256
- destinationTimestamp : undefined , // TODO
257
- nonce : parsed . nonce ,
258
- body : parsed . body ,
259
- originTransaction : {
260
- from : '0x' , //TODO
261
- transactionHash : log . transactionHash ,
262
- blockNumber : log . blockNumber ,
263
- gasUsed : 0 ,
264
- timestamp : 0 , //TODO
265
- } ,
266
- destinationTransaction : undefined , // TODO
279
+ status : MessageStatus . Unknown , // TODO
280
+ sender : message . sender ,
281
+ recipient : message . recipient ,
282
+ originDomainId : message . origin ,
283
+ destinationDomainId : message . destination ,
284
+ originChainId : multiProvider . getChainId ( message . origin ) ,
285
+ destinationChainId : multiProvider . getChainId ( message . destination ) ,
286
+ originTimestamp : tx . timestamp , // TODO
287
+ destinationTimestamp : 0 , // TODO
288
+ nonce : message . nonce ,
289
+ body : message . body ,
290
+ originTransaction : tx ,
291
+ destinationTransaction : emptyTx ,
267
292
} ;
268
293
}
0 commit comments