@@ -42,6 +42,7 @@ type Syncer struct {
4242 config config.Config
4343 genesis genesis.Genesis
4444 options common.BlockOptions
45+ logger zerolog.Logger
4546
4647 // State management
4748 lastState * atomic.Pointer [types.State ]
@@ -63,8 +64,8 @@ type Syncer struct {
6364 fiRetriever * da.ForcedInclusionRetriever
6465 p2pHandler p2pHandler
6566
66- // Logging
67- logger zerolog. Logger
67+ // Forced inclusion tracking
68+ pendingForcedInclusionTxs sync. Map // map[string]pendingForcedInclusionTx
6869
6970 // Lifecycle
7071 ctx context.Context
@@ -75,6 +76,14 @@ type Syncer struct {
7576 p2pWaitState atomic.Value // stores p2pWaitState
7677}
7778
79+ // pendingForcedInclusionTx represents a forced inclusion transaction that hasn't been included yet
80+ type pendingForcedInclusionTx struct {
81+ Data []byte
82+ EpochStart uint64
83+ EpochEnd uint64
84+ TxHash string
85+ }
86+
7887// NewSyncer creates a new block syncer
7988func NewSyncer (
8089 store store.Store ,
@@ -90,20 +99,23 @@ func NewSyncer(
9099 options common.BlockOptions ,
91100 errorCh chan <- error ,
92101) * Syncer {
102+ daRetrieverHeight := & atomic.Uint64 {}
103+ daRetrieverHeight .Store (genesis .DAStartHeight )
104+
93105 return & Syncer {
94106 store : store ,
95107 exec : exec ,
96- daClient : daClient ,
97108 cache : cache ,
98109 metrics : metrics ,
99110 config : config ,
100111 genesis : genesis ,
101112 options : options ,
113+ lastState : & atomic.Pointer [types.State ]{},
114+ daClient : daClient ,
115+ daRetrieverHeight : daRetrieverHeight ,
102116 headerStore : headerStore ,
103117 dataStore : dataStore ,
104- lastState : & atomic.Pointer [types.State ]{},
105- daRetrieverHeight : & atomic.Uint64 {},
106- heightInCh : make (chan common.DAHeightEvent , 1_000 ),
118+ heightInCh : make (chan common.DAHeightEvent , 100 ),
107119 errorCh : errorCh ,
108120 logger : logger .With ().Str ("component" , "syncer" ).Logger (),
109121 }
@@ -665,13 +677,16 @@ func hashTx(tx []byte) string {
665677 return hex .EncodeToString (hash [:])
666678}
667679
668- // verifyForcedInclusionTxs verifies that all forced inclusion transactions from DA are included in the block
680+ // verifyForcedInclusionTxs verifies that forced inclusion transactions from DA are properly handled.
681+ // Note: Due to block size constraints (MaxBytes), sequencers may defer forced inclusion transactions
682+ // to future blocks (smoothing). This is legitimate behavior within an epoch.
683+ // However, ALL forced inclusion txs from an epoch MUST be included before the next epoch begins.
669684func (s * Syncer ) verifyForcedInclusionTxs (currentState types.State , data * types.Data ) error {
670685 if s .fiRetriever == nil {
671686 return nil
672687 }
673688
674- // Retrieve forced inclusion transactions from DA
689+ // Retrieve forced inclusion transactions from DA for current epoch
675690 forcedIncludedTxsEvent , err := s .fiRetriever .RetrieveForcedIncludedTxs (s .ctx , currentState .DAHeight )
676691 if err != nil {
677692 if errors .Is (err , da .ErrForceInclusionNotConfigured ) {
@@ -682,42 +697,105 @@ func (s *Syncer) verifyForcedInclusionTxs(currentState types.State, data *types.
682697 return fmt .Errorf ("failed to retrieve forced included txs from DA: %w" , err )
683698 }
684699
685- // If no forced inclusion transactions found, nothing to verify
686- if len (forcedIncludedTxsEvent .Txs ) == 0 {
687- s .logger .Debug ().Uint64 ("da_height" , currentState .DAHeight ).Msg ("no forced inclusion transactions to verify" )
688- return nil
689- }
690-
700+ // Build map of transactions in current block
691701 blockTxMap := make (map [string ]struct {})
692702 for _ , tx := range data .Txs {
693703 blockTxMap [hashTx (tx )] = struct {}{}
694704 }
695705
696- // Check if all forced inclusion transactions are present in the block
697- var missingTxs [][]byte
706+ // Check if any pending forced inclusion txs from previous epochs are included
707+ var stillPending []pendingForcedInclusionTx
708+ s .pendingForcedInclusionTxs .Range (func (key , value interface {}) bool {
709+ pending := value .(pendingForcedInclusionTx )
710+ if _ , ok := blockTxMap [pending .TxHash ]; ok {
711+ s .logger .Debug ().
712+ Uint64 ("height" , data .Height ()).
713+ Uint64 ("epoch_start" , pending .EpochStart ).
714+ Uint64 ("epoch_end" , pending .EpochEnd ).
715+ Str ("tx_hash" , pending .TxHash [:16 ]).
716+ Msg ("pending forced inclusion transaction included in block" )
717+ s .pendingForcedInclusionTxs .Delete (key )
718+ } else {
719+ stillPending = append (stillPending , pending )
720+ }
721+ return true
722+ })
723+
724+ // Add new forced inclusion transactions from current epoch
725+ var newPendingCount , includedCount int
698726 for _ , forcedTx := range forcedIncludedTxsEvent .Txs {
699- if _ , ok := blockTxMap [hashTx (forcedTx )]; ! ok {
700- missingTxs = append (missingTxs , forcedTx )
727+ txHash := hashTx (forcedTx )
728+ if _ , ok := blockTxMap [txHash ]; ok {
729+ // Transaction is included in this block
730+ includedCount ++
731+ } else {
732+ // Transaction not included, add to pending
733+ stillPending = append (stillPending , pendingForcedInclusionTx {
734+ Data : forcedTx ,
735+ EpochStart : forcedIncludedTxsEvent .StartDaHeight ,
736+ EpochEnd : forcedIncludedTxsEvent .EndDaHeight ,
737+ TxHash : txHash ,
738+ })
739+ newPendingCount ++
701740 }
702741 }
703742
704- if len (missingTxs ) > 0 {
743+ // Check if we've moved past any epoch boundaries with pending txs
744+ var maliciousTxs , remainingPending []pendingForcedInclusionTx
745+ for _ , pending := range stillPending {
746+ // If current DA height is past this epoch's end, these txs should have been included
747+ if currentState .DAHeight > pending .EpochEnd {
748+ maliciousTxs = append (maliciousTxs , pending )
749+ } else {
750+ remainingPending = append (remainingPending , pending )
751+ }
752+ }
753+
754+ // Update pending map - clear old entries and store only remaining pending
755+ s .pendingForcedInclusionTxs .Range (func (key , value interface {}) bool {
756+ s .pendingForcedInclusionTxs .Delete (key )
757+ return true
758+ })
759+ for _ , pending := range remainingPending {
760+ s .pendingForcedInclusionTxs .Store (pending .TxHash , pending )
761+ }
762+
763+ // If there are transactions from past epochs that weren't included, sequencer is malicious
764+ if len (maliciousTxs ) > 0 {
705765 s .logger .Error ().
706766 Uint64 ("height" , data .Height ()).
707- Uint64 ("da_height" , currentState .DAHeight ).
708- Uint64 ("da_epoch_start" , forcedIncludedTxsEvent .StartDaHeight ).
709- Uint64 ("da_epoch_end" , forcedIncludedTxsEvent .EndDaHeight ).
710- Int ("missing_count" , len (missingTxs )).
711- Int ("total_forced" , len (forcedIncludedTxsEvent .Txs )).
712- Msg ("SEQUENCER IS MALICIOUS: forced inclusion transactions missing from block" )
713- return errors .Join (errMaliciousProposer , fmt .Errorf ("sequencer is malicious: %d forced inclusion transactions not included in block" , len (missingTxs )))
767+ Uint64 ("current_da_height" , currentState .DAHeight ).
768+ Int ("malicious_count" , len (maliciousTxs )).
769+ Msg ("SEQUENCER IS MALICIOUS: forced inclusion transactions from past epoch(s) not included" )
770+ return errors .Join (errMaliciousProposer , fmt .Errorf ("sequencer is malicious: %d forced inclusion transactions from past epoch(s) not included" , len (maliciousTxs )))
771+ }
772+
773+ // Log current state
774+ if len (forcedIncludedTxsEvent .Txs ) > 0 {
775+ if newPendingCount > 0 {
776+ totalPending := 0
777+ s .pendingForcedInclusionTxs .Range (func (key , value interface {}) bool {
778+ totalPending ++
779+ return true
780+ })
781+
782+ s .logger .Info ().
783+ Uint64 ("height" , data .Height ()).
784+ Uint64 ("da_height" , currentState .DAHeight ).
785+ Uint64 ("epoch_start" , forcedIncludedTxsEvent .StartDaHeight ).
786+ Uint64 ("epoch_end" , forcedIncludedTxsEvent .EndDaHeight ).
787+ Int ("included_count" , includedCount ).
788+ Int ("deferred_count" , newPendingCount ).
789+ Int ("total_pending" , totalPending ).
790+ Msg ("forced inclusion transactions processed - some deferred due to block size constraints" )
791+ } else {
792+ s .logger .Debug ().
793+ Uint64 ("height" , data .Height ()).
794+ Int ("forced_txs" , len (forcedIncludedTxsEvent .Txs )).
795+ Msg ("all forced inclusion transactions included in block" )
796+ }
714797 }
715798
716- s .logger .Debug ().
717- Uint64 ("height" , data .Height ()).
718- Int ("forced_txs" , len (forcedIncludedTxsEvent .Txs )).
719- Msg ("all forced inclusion transactions verified in block" )
720-
721799 return nil
722800}
723801
0 commit comments