@@ -110,6 +110,7 @@ type Syncer struct {
110110 gracePeriodMultiplier * atomic.Pointer [float64 ]
111111 blockFullnessEMA * atomic.Pointer [float64 ]
112112 gracePeriodConfig forcedInclusionGracePeriodConfig
113+ p2pHeightHints map [uint64 ]uint64 // map[height]daHeight
113114
114115 // Lifecycle
115116 ctx context.Context
@@ -177,6 +178,7 @@ func NewSyncer(
177178 gracePeriodMultiplier : gracePeriodMultiplier ,
178179 blockFullnessEMA : blockFullnessEMA ,
179180 gracePeriodConfig : newForcedInclusionGracePeriodConfig (),
181+ p2pHeightHints : make (map [uint64 ]uint64 ),
180182 }
181183}
182184
@@ -541,6 +543,8 @@ func (s *Syncer) processHeightEvent(event *common.DAHeightEvent) {
541543 Uint64 ("da_height_hint" , daHeightHint ).
542544 Msg ("P2P event with DA height hint, triggering targeted DA retrieval" )
543545
546+ s .p2pHeightHints [height ] = daHeightHint
547+
544548 // Trigger targeted DA retrieval in background via worker pool
545549 s .asyncDARetriever .RequestRetrieval (daHeightHint )
546550 }
@@ -581,7 +585,7 @@ func (s *Syncer) processHeightEvent(event *common.DAHeightEvent) {
581585 }
582586
583587 // only save to p2p stores if the event came from DA
584- if event .Source == common .SourceDA { // TODO(@julienrbrt): To be reverted once DA Hints are merged (https://github.com/evstack/ev-node/pull/2891)
588+ if event .Source == common .SourceDA {
585589 g , ctx := errgroup .WithContext (s .ctx )
586590 g .Go (func () error {
587591 // broadcast header locally only — prevents spamming the p2p network with old height notifications,
@@ -636,13 +640,17 @@ func (s *Syncer) trySyncNextBlock(event *common.DAHeightEvent) error {
636640 }
637641
638642 // Verify forced inclusion transactions if configured
639- if event .Source == common .SourceDA {
640- if err := s .verifyForcedInclusionTxs (currentState , data ); err != nil {
641- s .logger .Error ().Err (err ).Uint64 ("height" , nextHeight ).Msg ("forced inclusion verification failed" )
642- if errors .Is (err , errMaliciousProposer ) {
643- s .cache .RemoveHeaderDAIncluded (headerHash )
644- return err
645- }
643+ currentDaHeight , ok := s .p2pHeightHints [nextHeight ]
644+ if ! ok {
645+ currentDaHeight = currentState .DAHeight
646+ } else {
647+ delete (s .p2pHeightHints , nextHeight )
648+ }
649+ if err := s .verifyForcedInclusionTxs (currentDaHeight , data ); err != nil {
650+ s .logger .Error ().Err (err ).Uint64 ("height" , nextHeight ).Msg ("forced inclusion verification failed" )
651+ if errors .Is (err , errMaliciousProposer ) {
652+ s .cache .RemoveHeaderDAIncluded (headerHash )
653+ return err
646654 }
647655 }
648656
@@ -855,7 +863,7 @@ func (s *Syncer) getEffectiveGracePeriod() uint64 {
855863// Note: Due to block size constraints (MaxBytes), sequencers may defer forced inclusion transactions
856864// to future blocks (smoothing). This is legitimate behavior within an epoch.
857865// However, ALL forced inclusion txs from an epoch MUST be included before the next epoch begins or grace boundary (whichever comes later).
858- func (s * Syncer ) verifyForcedInclusionTxs (currentState types. State , data * types.Data ) error {
866+ func (s * Syncer ) verifyForcedInclusionTxs (daHeight uint64 , data * types.Data ) error {
859867 if s .fiRetriever == nil {
860868 return nil
861869 }
@@ -865,7 +873,7 @@ func (s *Syncer) verifyForcedInclusionTxs(currentState types.State, data *types.
865873 s .updateDynamicGracePeriod (blockFullness )
866874
867875 // Retrieve forced inclusion transactions from DA for current epoch
868- forcedIncludedTxsEvent , err := s .fiRetriever .RetrieveForcedIncludedTxs (s .ctx , currentState . DAHeight )
876+ forcedIncludedTxsEvent , err := s .fiRetriever .RetrieveForcedIncludedTxs (s .ctx , daHeight )
869877 if err != nil {
870878 if errors .Is (err , da .ErrForceInclusionNotConfigured ) {
871879 s .logger .Debug ().Msg ("forced inclusion namespace not configured, skipping verification" )
@@ -928,10 +936,10 @@ func (s *Syncer) verifyForcedInclusionTxs(currentState types.State, data *types.
928936 effectiveGracePeriod := s .getEffectiveGracePeriod ()
929937 graceBoundary := pending .EpochEnd + (effectiveGracePeriod * s .genesis .DAEpochForcedInclusion )
930938
931- if currentState . DAHeight > graceBoundary {
939+ if daHeight > graceBoundary {
932940 maliciousTxs = append (maliciousTxs , pending )
933941 s .logger .Warn ().
934- Uint64 ("current_da_height" , currentState . DAHeight ).
942+ Uint64 ("current_da_height" , daHeight ).
935943 Uint64 ("epoch_end" , pending .EpochEnd ).
936944 Uint64 ("grace_boundary" , graceBoundary ).
937945 Uint64 ("base_grace_periods" , s .gracePeriodConfig .basePeriod ).
@@ -941,7 +949,7 @@ func (s *Syncer) verifyForcedInclusionTxs(currentState types.State, data *types.
941949 Msg ("forced inclusion transaction past grace boundary - marking as malicious" )
942950 } else {
943951 remainingPending = append (remainingPending , pending )
944- if currentState . DAHeight > pending .EpochEnd {
952+ if daHeight > pending .EpochEnd {
945953 txsInGracePeriod ++
946954 }
947955 }
@@ -965,7 +973,7 @@ func (s *Syncer) verifyForcedInclusionTxs(currentState types.State, data *types.
965973 effectiveGracePeriod := s .getEffectiveGracePeriod ()
966974 s .logger .Error ().
967975 Uint64 ("height" , data .Height ()).
968- Uint64 ("current_da_height" , currentState . DAHeight ).
976+ Uint64 ("current_da_height" , daHeight ).
969977 Int ("malicious_count" , len (maliciousTxs )).
970978 Uint64 ("base_grace_periods" , s .gracePeriodConfig .basePeriod ).
971979 Uint64 ("effective_grace_periods" , effectiveGracePeriod ).
@@ -985,7 +993,7 @@ func (s *Syncer) verifyForcedInclusionTxs(currentState types.State, data *types.
985993
986994 s .logger .Info ().
987995 Uint64 ("height" , data .Height ()).
988- Uint64 ("da_height" , currentState . DAHeight ).
996+ Uint64 ("da_height" , daHeight ).
989997 Uint64 ("epoch_start" , forcedIncludedTxsEvent .StartDaHeight ).
990998 Uint64 ("epoch_end" , forcedIncludedTxsEvent .EndDaHeight ).
991999 Int ("included_count" , includedCount ).
0 commit comments