@@ -160,7 +160,7 @@ func (r *RPCEventSubscriber) subscribe(ctx context.Context, height uint64) <-cha
160160 return
161161 }
162162
163- evmEvents := models .NewBlockEvents (blockEvents )
163+ evmEvents := models .NewSingleBlockEvents (blockEvents )
164164 // if events contain an error, or we are in a recovery mode
165165 if evmEvents .Err != nil || r .recovery {
166166 evmEvents = r .recover (ctx , blockEvents , evmEvents .Err )
@@ -237,6 +237,18 @@ const maxRangeForGetEvents = uint64(249)
237237func (r * RPCEventSubscriber ) backfillSporkFromHeight (ctx context.Context , fromCadenceHeight uint64 , eventsChan chan <- models.BlockEvents ) (uint64 , error ) {
238238 evmAddress := common .Address (systemcontracts .SystemContractsForChain (r .chain ).EVMContract .Address )
239239
240+ blockExecutedEvent := common .NewAddressLocation (
241+ nil ,
242+ evmAddress ,
243+ string (events .EventTypeBlockExecuted ),
244+ ).ID ()
245+
246+ transactionExecutedEvent := common .NewAddressLocation (
247+ nil ,
248+ evmAddress ,
249+ string (events .EventTypeTransactionExecuted ),
250+ ).ID ()
251+
240252 lastHeight , err := r .client .GetLatestHeightForSpork (ctx , fromCadenceHeight )
241253 if err != nil {
242254 eventsChan <- models .NewBlockEventsError (err )
@@ -257,18 +269,6 @@ func (r *RPCEventSubscriber) backfillSporkFromHeight(ctx context.Context, fromCa
257269 endHeight = lastHeight
258270 }
259271
260- blockExecutedEvent := common .NewAddressLocation (
261- nil ,
262- evmAddress ,
263- string (events .EventTypeBlockExecuted ),
264- ).ID ()
265-
266- transactionExecutedEvent := common .NewAddressLocation (
267- nil ,
268- evmAddress ,
269- string (events .EventTypeTransactionExecuted ),
270- ).ID ()
271-
272272 blocks , err := r .client .GetEventsForHeightRange (ctx , blockExecutedEvent , startHeight , endHeight )
273273 if err != nil {
274274 return 0 , fmt .Errorf ("failed to get block events: %w" , err )
@@ -299,7 +299,22 @@ func (r *RPCEventSubscriber) backfillSporkFromHeight(ctx context.Context, fromCa
299299 // append the transaction events to the block events
300300 blocks [i ].Events = append (blocks [i ].Events , transactions [i ].Events ... )
301301
302- evmEvents := models .NewBlockEvents (blocks [i ])
302+ evmEvents := models .NewSingleBlockEvents (blocks [i ])
303+ if evmEvents .Err != nil && errors .Is (evmEvents .Err , errs .ErrMissingBlock ) {
304+ evmEvents , err = r .accumulateBlockEvents (
305+ ctx ,
306+ blocks [i ],
307+ blockExecutedEvent ,
308+ transactionExecutedEvent ,
309+ )
310+ if err != nil {
311+ return 0 , err
312+ }
313+ eventsChan <- evmEvents
314+ // advance the height
315+ fromCadenceHeight = evmEvents .Events .CadenceHeight () + 1
316+ break
317+ }
303318 eventsChan <- evmEvents
304319
305320 // advance the height
@@ -310,6 +325,86 @@ func (r *RPCEventSubscriber) backfillSporkFromHeight(ctx context.Context, fromCa
310325 return fromCadenceHeight , nil
311326}
312327
328+ // accumulateBlockEvents will keep fetching `EVM.TransactionExecuted` events
329+ // until it finds their `EVM.BlockExecuted` event.
330+ // At that point it will return the valid models.BlockEvents.
331+ func (r * RPCEventSubscriber ) accumulateBlockEvents (
332+ ctx context.Context ,
333+ block flow.BlockEvents ,
334+ blockExecutedEventType string ,
335+ txExecutedEventType string ,
336+ ) (models.BlockEvents , error ) {
337+ evmEvents := models .NewSingleBlockEvents (block )
338+ currentHeight := block .Height
339+ transactionEvents := make ([]flow.Event , 0 )
340+
341+ for evmEvents .Err != nil && errors .Is (evmEvents .Err , errs .ErrMissingBlock ) {
342+ blocks , err := r .client .GetEventsForHeightRange (
343+ ctx ,
344+ blockExecutedEventType ,
345+ currentHeight ,
346+ currentHeight + maxRangeForGetEvents ,
347+ )
348+ if err != nil {
349+ return models.BlockEvents {}, fmt .Errorf ("failed to get block events: %w" , err )
350+ }
351+
352+ transactions , err := r .client .GetEventsForHeightRange (
353+ ctx ,
354+ txExecutedEventType ,
355+ currentHeight ,
356+ currentHeight + maxRangeForGetEvents ,
357+ )
358+ if err != nil {
359+ return models.BlockEvents {}, fmt .Errorf ("failed to get block events: %w" , err )
360+ }
361+
362+ if len (transactions ) != len (blocks ) {
363+ return models.BlockEvents {}, fmt .Errorf ("transactions and blocks have different length" )
364+ }
365+
366+ // sort both, just in case
367+ sort .Slice (blocks , func (i , j int ) bool {
368+ return blocks [i ].Height < blocks [j ].Height
369+ })
370+ sort .Slice (transactions , func (i , j int ) bool {
371+ return transactions [i ].Height < transactions [j ].Height
372+ })
373+
374+ for i := range blocks {
375+ if transactions [i ].Height != blocks [i ].Height {
376+ return models.BlockEvents {}, fmt .Errorf ("transactions and blocks have different height" )
377+ }
378+
379+ // If no EVM.BlockExecuted event found, keep accumulating the incoming
380+ // EVM.TransactionExecuted events, until we find the EVM.BlockExecuted
381+ // event that includes them.
382+ if len (blocks [i ].Events ) == 0 {
383+ txEvents := transactions [i ].Events
384+ // Sort `EVM.TransactionExecuted` events
385+ sort .Slice (txEvents , func (i , j int ) bool {
386+ if txEvents [i ].TransactionIndex != txEvents [j ].TransactionIndex {
387+ return txEvents [i ].TransactionIndex < txEvents [j ].TransactionIndex
388+ }
389+ return txEvents [i ].EventIndex < txEvents [j ].EventIndex
390+ })
391+ transactionEvents = append (transactionEvents , txEvents ... )
392+ } else {
393+ blocks [i ].Events = append (blocks [i ].Events , transactionEvents ... )
394+ // We use `models.NewMultiBlockEvents`, as the `transactionEvents`
395+ // are coming from different Flow blocks.
396+ evmEvents = models .NewMultiBlockEvents (blocks [i ])
397+ if evmEvents .Err == nil {
398+ return evmEvents , nil
399+ }
400+ }
401+
402+ currentHeight = blocks [i ].Height + 1
403+ }
404+ }
405+ return evmEvents , nil
406+ }
407+
313408// fetchMissingData is used as a backup mechanism for fetching EVM-related
314409// events, when the event streaming API returns an inconsistent response.
315410// An inconsistent response could be an EVM block that references EVM
@@ -346,17 +441,27 @@ func (r *RPCEventSubscriber) fetchMissingData(
346441 blockEvents .Events = append (blockEvents .Events , recoveredEvents [0 ].Events ... )
347442 }
348443
349- return models .NewBlockEvents (blockEvents )
444+ return models .NewSingleBlockEvents (blockEvents )
350445}
351446
352447// accumulateEventsMissingBlock will keep receiving transaction events until it can produce a valid
353448// EVM block event containing a block and transactions. At that point it will reset the recovery mode
354449// and return the valid block events.
355450func (r * RPCEventSubscriber ) accumulateEventsMissingBlock (events flow.BlockEvents ) models.BlockEvents {
356- r .recoveredEvents = append (r .recoveredEvents , events .Events ... )
451+ txEvents := events .Events
452+ // Sort `EVM.TransactionExecuted` events
453+ sort .Slice (txEvents , func (i , j int ) bool {
454+ if txEvents [i ].TransactionIndex != txEvents [j ].TransactionIndex {
455+ return txEvents [i ].TransactionIndex < txEvents [j ].TransactionIndex
456+ }
457+ return txEvents [i ].EventIndex < txEvents [j ].EventIndex
458+ })
459+ r .recoveredEvents = append (r .recoveredEvents , txEvents ... )
357460 events .Events = r .recoveredEvents
358461
359- recovered := models .NewBlockEvents (events )
462+ // We use `models.NewMultiBlockEvents`, as the `transactionEvents`
463+ // are coming from different Flow blocks.
464+ recovered := models .NewMultiBlockEvents (events )
360465 r .recovery = recovered .Err != nil
361466
362467 if ! r .recovery {
0 commit comments