@@ -42,12 +42,21 @@ import { LayerZeroEnpointV2Interface, PacketSentEvent } from 'src/contracts/Laye
42
42
import { STATUS_LOG_INTERVAL } from 'src/logger/logger.service' ;
43
43
import { calculatePayloadHash , decodeHeader , decodePacket } from './layer-zero.utils' ;
44
44
45
+ const ON_PACKET_SENT_PROCESSED_CHANNEL = 'packet_sent_processed' ;
46
+ const ON_PACKET_SENT_PROCESSED_DELAY = 30 * 1000 ;
47
+ const MAX_PENDING_PAYLOAD_VERIFIED_EVENT_DURATION = 6 * 60 * 60 * 1000 ;
45
48
46
49
interface LayerZeroPayloadData {
47
50
messageIdentifier : string ,
48
51
payload : string ,
49
52
}
50
53
54
+ interface PayloadVerifiedEvent {
55
+ timestamp : number ,
56
+ payloadHash : string ,
57
+ log : Log ,
58
+ }
59
+
51
60
class LayerZeroWorker {
52
61
private readonly config : LayerZeroWorkerData ;
53
62
@@ -71,6 +80,11 @@ class LayerZeroWorker {
71
80
private readonly layerZeroChainIdMap : Record < string , string > ;
72
81
private readonly incentivesAddresses : Record < string , string > ;
73
82
83
+ // Keep track of unprocessed PayloadVerified events caused by not having processed yet the
84
+ // corresponding PacketSent event on the source chain. This is most relevant for when
85
+ // recovering past relays.
86
+ private readonly pendingPayloadVerifiedEvents : PayloadVerifiedEvent [ ] = [ ] ;
87
+
74
88
private currentStatus : MonitorStatus | null = null ;
75
89
private monitor : MonitorInterface ;
76
90
@@ -201,6 +215,8 @@ class LayerZeroWorker {
201
215
`LayerZero collector worker started.` ,
202
216
) ;
203
217
218
+ await this . listenForProcessedPackets ( ) ;
219
+
204
220
this . fromBlock = await this . getStartingBlock ( ) ;
205
221
const stopBlock = this . config . stoppingBlock ?? Infinity ;
206
222
@@ -330,6 +346,65 @@ class LayerZeroWorker {
330
346
return logs ;
331
347
}
332
348
349
+ private async listenForProcessedPackets ( ) : Promise < void > {
350
+ // Listen for whenever a packet is registered.
351
+ await this . store . on (
352
+ this . getOnPacketSentChannel ( ) ,
353
+ ( payloadHash : string ) => {
354
+ // Add a delay to prevent this handler from being executed at the exact same time
355
+ // as the `handlePayloadVerifiedEvent()` handler, which can cause this handler to
356
+ // search for a pending PayloadVerified event before it's registered.
357
+ setTimeout (
358
+ ( ) => void this . onProcessedPacketHandler ( payloadHash ) ,
359
+ ON_PACKET_SENT_PROCESSED_DELAY
360
+ ) ;
361
+ }
362
+ )
363
+ }
364
+
365
+ private async onProcessedPacketHandler (
366
+ payloadHash : string
367
+ ) : Promise < void > {
368
+
369
+ this . logger . debug (
370
+ { payloadHash } ,
371
+ `On PacketSent event recovery handler triggered.`
372
+ ) ;
373
+
374
+ const pendingPayloadVerifiedEventIndex = this . pendingPayloadVerifiedEvents . findIndex (
375
+ ( event ) => event . payloadHash === payloadHash ,
376
+ ) ;
377
+
378
+ if ( pendingPayloadVerifiedEventIndex == - 1 ) {
379
+ return ;
380
+ }
381
+
382
+ this . logger . info (
383
+ { payloadHash } ,
384
+ `Recovering PayloadVerified event.`
385
+ ) ;
386
+
387
+ const [ pendingEvent ] = this . pendingPayloadVerifiedEvents . splice ( pendingPayloadVerifiedEventIndex , 1 ) ;
388
+
389
+ const parsedLog = this . receiveULN302Interface . parseLog ( pendingEvent ! . log ) ;
390
+
391
+ try {
392
+ await this . handlePayloadVerifiedEvent (
393
+ pendingEvent ! . log ,
394
+ parsedLog ! // The log has been previously parsed, `parsedLog` should never be null.
395
+ ) ;
396
+ }
397
+ catch ( error ) {
398
+ this . logger . error (
399
+ {
400
+ payloadHash,
401
+ error : tryErrorToString ( error ) ,
402
+ } ,
403
+ `Error on PayloadVerified event recovery.`
404
+ ) ;
405
+ }
406
+ }
407
+
333
408
334
409
335
410
// Event handlers
@@ -463,8 +538,8 @@ class LayerZeroWorker {
463
538
messageIdentifier,
464
539
465
540
amb : 'layer-zero' ,
466
- fromChainId : fromChainId . toString ( ) ,
467
- toChainId : toChainId . toString ( ) ,
541
+ fromChainId,
542
+ toChainId,
468
543
fromIncentivesAddress : packet . sender ,
469
544
toIncentivesAddress,
470
545
@@ -484,17 +559,31 @@ class LayerZeroWorker {
484
559
485
560
await this . store . setAdditionalAMBData < LayerZeroPayloadData > (
486
561
'layer-zero' ,
487
- payloadHash . toLowerCase ( ) ,
562
+ payloadHash ,
488
563
{
489
564
messageIdentifier,
490
565
payload : encodedPayload
491
566
} ,
492
567
) ;
568
+
569
+ // Broadcast that the PacketSent event has been processed to recover any pending logic
570
+ // resulting from PayloadVerified events.
571
+ await this . store . postMessage (
572
+ this . getOnPacketSentChannel ( ) ,
573
+ payloadHash
574
+ ) ;
493
575
}
494
576
495
577
/**
496
578
* Handles PayloadVerified events.
497
579
*
580
+ * ! A PayloadVerified event is emitted every time a specific packet is verified, but a single
581
+ * ! event may not be enough to indicate that the packet is valid. Thus, there may be multiple
582
+ * ! events for a single packet, which, depending at the time at which they are processed, can
583
+ * ! result in this function submitting the proof for the same packet multiple times. This
584
+ * ! undesired side effect is mitigated by the Store's `setAMBProof()` function, which will not
585
+ * ! register proofs for the same packet more than once.
586
+ *
498
587
* @param log - The log data.
499
588
* @param parsedLog - The parsed log description.
500
589
*/
@@ -544,24 +633,22 @@ class LayerZeroWorker {
544
633
return ;
545
634
}
546
635
547
- this . logger . info (
548
- {
549
- transactionHash : log . transactionHash ,
550
- payloadHash,
551
- } ,
552
- 'PayloadVerified event decoded.' ,
553
- ) ;
554
-
555
636
// Recover the encoded payload data from storage (saved on an earlier PacketSent event).
556
637
const payloadData = await this . store . getAdditionalAMBData < LayerZeroPayloadData > (
557
638
'layer-zero' ,
558
639
payloadHash . toLowerCase ( )
559
640
) ;
560
641
if ( ! payloadData ) {
561
- this . logger . warn (
642
+ this . logger . info (
562
643
{ payloadHash } ,
563
- 'No payload data found for the given payloadHash.' ,
644
+ 'No payload data found for the given payloadHash. Queueing for recovery for when the payload is available. ' ,
564
645
) ;
646
+
647
+ this . queuePendingPayloadVerifiedEvent (
648
+ payloadHash ,
649
+ log ,
650
+ ) ;
651
+
565
652
return ;
566
653
}
567
654
@@ -581,8 +668,8 @@ class LayerZeroWorker {
581
668
messageIdentifier : payloadData . messageIdentifier ,
582
669
583
670
amb : 'layer-zero' ,
584
- fromChainId : fromChainId . toString ( ) ,
585
- toChainId : toChainId . toString ( ) ,
671
+ fromChainId,
672
+ toChainId,
586
673
587
674
message : payloadData . payload ,
588
675
messageCtx : '0x' ,
@@ -610,6 +697,47 @@ class LayerZeroWorker {
610
697
) ;
611
698
}
612
699
}
700
+
701
+ private queuePendingPayloadVerifiedEvent (
702
+ payloadHash : string ,
703
+ log : Log ,
704
+ ) : void {
705
+ // Prune any old pending events (note that events are stored in chronological order).
706
+ const pruneTimestamp = Date . now ( ) - MAX_PENDING_PAYLOAD_VERIFIED_EVENT_DURATION ;
707
+
708
+ let firstNonStaleIndex ;
709
+ for (
710
+ firstNonStaleIndex = 0 ;
711
+ firstNonStaleIndex < this . pendingPayloadVerifiedEvents . length ;
712
+ firstNonStaleIndex ++
713
+ ) {
714
+ if ( this . pendingPayloadVerifiedEvents [ firstNonStaleIndex ] ! . timestamp > pruneTimestamp ) {
715
+ break ;
716
+ }
717
+ }
718
+
719
+ if ( firstNonStaleIndex != 0 ) {
720
+ this . pendingPayloadVerifiedEvents . splice ( 0 , firstNonStaleIndex ) ;
721
+ }
722
+
723
+
724
+ // Register the pending event if not already pending, otherwise update the pending's event
725
+ // 'timestamp'.
726
+ const alreadyPendingEvent = this . pendingPayloadVerifiedEvents . find ( ( event ) => {
727
+ event . payloadHash === payloadHash
728
+ } ) ;
729
+
730
+ if ( alreadyPendingEvent != undefined ) {
731
+ alreadyPendingEvent . timestamp = Date . now ( ) ;
732
+ }
733
+ else {
734
+ this . pendingPayloadVerifiedEvents . push ( {
735
+ timestamp : Date . now ( ) ,
736
+ payloadHash,
737
+ log,
738
+ } ) ;
739
+ }
740
+ }
613
741
614
742
615
743
async checkIfVerifiable (
@@ -684,6 +812,9 @@ class LayerZeroWorker {
684
812
throw new Error ( `Failed to query the ULN configuration. (dvn: ${ dvn } , destination eid: ${ dstEid } ).` ) ;
685
813
}
686
814
815
+ private getOnPacketSentChannel ( ) : string {
816
+ return Store . getChannel ( 'layer-zero' , ON_PACKET_SENT_PROCESSED_CHANNEL ) ;
817
+ }
687
818
688
819
689
820
// Misc Helpers
0 commit comments