1
1
import { decode } from 'bs58' ;
2
2
import { Provider } from 'near-api-js/lib/providers' ;
3
- import { BlockResult } from 'near-api-js/lib/providers/provider' ;
3
+ import { BlockResult , ExecutionStatus } from 'near-api-js/lib/providers/provider' ;
4
4
import { z } from 'zod' ;
5
5
import { VaasByBlock } from '../databases/types' ;
6
- import { makeBlockKey } from '../databases/utils' ;
6
+ import { makeBlockKey , makeVaaKey } from '../databases/utils' ;
7
7
import {
8
8
fetchBlockByBlockId ,
9
- getMessagesFromBlockResults ,
10
9
getNearProvider ,
11
10
getTimestampByBlock ,
11
+ isWormholePublishEventLog ,
12
12
} from '../utils/near' ;
13
13
import { Watcher } from './Watcher' ;
14
14
import { assertEnvironmentVariable , sleep } from '@wormhole-foundation/wormhole-monitor-common' ;
15
15
import { Network , contracts } from '@wormhole-foundation/sdk-base' ;
16
16
import axios from 'axios' ;
17
- import { AXIOS_CONFIG_JSON } from '../consts' ;
17
+ import { AXIOS_CONFIG_JSON , HB_INTERVAL } from '../consts' ;
18
+ import { EventLog } from 'src/types/near' ;
18
19
19
20
export class NearArchiveWatcher extends Watcher {
20
21
provider : Provider | null = null ;
21
22
22
23
constructor ( network : Network ) {
23
24
super ( network , 'Near' ) ;
24
- this . maximumBatchSize = 1000 ;
25
+ this . maximumBatchSize = 1_000_000 ;
26
+ this . watchLoopDelay = 60 * 60 * 1000 ; // 1 hour
25
27
}
26
28
27
29
async getFinalizedBlockNumber ( ) : Promise < number > {
@@ -37,7 +39,11 @@ export class NearArchiveWatcher extends Watcher {
37
39
}
38
40
}
39
41
40
- async getMessagesForBlocks ( fromBlock : number , toBlock : number ) : Promise < VaasByBlock > {
42
+ async getMessagesForBlocks (
43
+ fromBlock : number ,
44
+ toBlock : number
45
+ ) : Promise < { vaasByBlock : VaasByBlock ; optionalBlockHeight ?: number } > {
46
+ const quittingTimestamp = Date . now ( ) + HB_INTERVAL * 0.75 ;
41
47
const origFromBlock = fromBlock ;
42
48
const origToBlock = toBlock ;
43
49
this . logger . info ( `fetching info for blocks ${ origFromBlock } to ${ origToBlock } ` ) ;
@@ -114,21 +120,26 @@ export class NearArchiveWatcher extends Watcher {
114
120
}
115
121
116
122
this . logger . info ( `Fetched ${ blocks . length } blocks` ) ;
117
- const vaasByBlock : VaasByBlock = await getMessagesFromBlockResults (
123
+ const response : ConstrainedResponse = await this . getMessagesFromBlockResultsConstrained (
118
124
this . network ,
119
125
provider ,
120
126
blocks ,
121
- true
127
+ quittingTimestamp
122
128
) ;
129
+ // This is the case where there are no transactions in the time window.
130
+ if ( response . lastBlockHeight === 0 ) {
131
+ response . lastBlockHeight = toBlock ;
132
+ }
133
+ const lastBlockInfo = await fetchBlockByBlockId ( provider , response . lastBlockHeight ) ;
123
134
// Make a block for the to_block, if it isn't already there
124
135
const blockKey = makeBlockKey (
125
- toBlockInfo . header . height . toString ( ) ,
126
- new Date ( toBlockInfo . header . timestamp / 1_000_000 ) . toISOString ( )
136
+ response . lastBlockHeight . toString ( ) ,
137
+ new Date ( lastBlockInfo . header . timestamp / 1_000_000 ) . toISOString ( )
127
138
) ;
128
- if ( ! vaasByBlock [ blockKey ] ) {
129
- vaasByBlock [ blockKey ] = [ ] ;
139
+ if ( ! response . vaasByBlock [ blockKey ] ) {
140
+ response . vaasByBlock [ blockKey ] = [ ] ;
130
141
}
131
- return vaasByBlock ;
142
+ return response ;
132
143
}
133
144
134
145
async getProvider ( ) : Promise < Provider > {
@@ -191,7 +202,90 @@ export class NearArchiveWatcher extends Watcher {
191
202
}
192
203
return txs . reverse ( ) ;
193
204
}
205
+
206
+ async getMessagesFromBlockResultsConstrained (
207
+ network : Network ,
208
+ provider : Provider ,
209
+ blocks : BlockResult [ ] ,
210
+ quittingTime : number
211
+ ) : Promise < ConstrainedResponse > {
212
+ const vaasByBlock : VaasByBlock = { } ;
213
+ let lastBlockHeight = 0 ;
214
+ let prevLastBlockHeight = 0 ;
215
+ this . logger . debug ( `Fetching messages from ${ blocks . length } blocks...` ) ;
216
+ try {
217
+ for ( let i = 0 ; i < blocks . length ; i ++ ) {
218
+ this . logger . debug ( `Fetching messages from block ${ i + 1 } /${ blocks . length } ...` ) ;
219
+ const { height, timestamp } = blocks [ i ] . header ;
220
+ prevLastBlockHeight = lastBlockHeight ;
221
+ lastBlockHeight = height ;
222
+ const blockKey = makeBlockKey (
223
+ height . toString ( ) ,
224
+ new Date ( timestamp / 1_000_000 ) . toISOString ( )
225
+ ) ;
226
+ let localVaasByBlock : VaasByBlock = { } ;
227
+ localVaasByBlock [ blockKey ] = [ ] ;
228
+
229
+ const chunks = [ ] ;
230
+ this . logger . debug ( 'attempting to fetch chunks' ) ;
231
+ for ( const chunk of blocks [ i ] . chunks ) {
232
+ chunks . push ( await provider . chunk ( chunk . chunk_hash ) ) ;
233
+ }
234
+
235
+ const transactions = chunks . flatMap ( ( { transactions } ) => transactions ) ;
236
+ const coreBridge = contracts . coreBridge . get ( network , 'Near' ) ;
237
+ if ( ! coreBridge ) {
238
+ throw new Error ( 'Unable to get contract address for Near' ) ;
239
+ }
240
+ this . logger . debug ( `attempting to fetch ${ transactions . length } transactions` ) ;
241
+ const totTx = transactions . length ;
242
+ let txCount = 1 ;
243
+ for ( const tx of transactions ) {
244
+ this . logger . debug ( `fetching transaction ${ txCount } /${ totTx } ` ) ;
245
+ txCount ++ ;
246
+ const outcome = await provider . txStatus ( tx . hash , coreBridge ) ;
247
+ const logs = outcome . receipts_outcome
248
+ . filter (
249
+ ( { outcome } ) =>
250
+ ( outcome as any ) . executor_id === coreBridge &&
251
+ ( outcome . status as ExecutionStatus ) . SuccessValue
252
+ )
253
+ . flatMap ( ( { outcome } ) => outcome . logs )
254
+ . filter ( ( log ) => log . startsWith ( 'EVENT_JSON:' ) ) // https://nomicon.io/Standards/EventsFormat
255
+ . map ( ( log ) => JSON . parse ( log . slice ( 11 ) ) as EventLog )
256
+ . filter ( isWormholePublishEventLog ) ;
257
+ for ( const log of logs ) {
258
+ const vaaKey = makeVaaKey ( tx . hash , 'Near' , log . emitter , log . seq . toString ( ) ) ;
259
+ localVaasByBlock [ blockKey ] = [ ...localVaasByBlock [ blockKey ] , vaaKey ] ;
260
+ }
261
+ }
262
+ this . logger . debug (
263
+ `Fetched ${ localVaasByBlock [ blockKey ] . length } messages from block ${ blockKey } `
264
+ ) ;
265
+ vaasByBlock [ blockKey ] = localVaasByBlock [ blockKey ] ;
266
+ if ( Date . now ( ) >= quittingTime ) {
267
+ this . logger . warn ( `Quitting early due to time constraint.` ) ;
268
+ break ;
269
+ }
270
+ }
271
+ } catch ( e ) {
272
+ this . logger . error ( `Near block getMessagesFromBlockResultsConstrained error: ${ e } ` ) ;
273
+ this . logger . warn ( `Quitting early due to error.` ) ;
274
+ lastBlockHeight = prevLastBlockHeight ;
275
+ }
276
+
277
+ const numMessages = Object . values ( vaasByBlock ) . flat ( ) . length ;
278
+ this . logger . debug ( `Fetched ${ numMessages } messages from ${ blocks . length } blocks` ) ;
279
+
280
+ return { vaasByBlock, lastBlockHeight } ;
281
+ }
194
282
}
283
+
284
+ type ConstrainedResponse = {
285
+ vaasByBlock : VaasByBlock ;
286
+ lastBlockHeight : number ;
287
+ } ;
288
+
195
289
type GetTransactionsByAccountIdResponse = {
196
290
txns : NearTxn [ ] ;
197
291
} ;
0 commit comments