@@ -5,11 +5,12 @@ import (
55 "context"
66 "errors"
77 "fmt"
8- pubsub "github.com/libp2p/go-libp2p-pubsub"
98 "sync"
109 "sync/atomic"
1110 "time"
1211
12+ pubsub "github.com/libp2p/go-libp2p-pubsub"
13+
1314 "github.com/rs/zerolog"
1415 "golang.org/x/sync/errgroup"
1516
@@ -472,6 +473,12 @@ func (s *Syncer) trySyncNextBlock(event *common.DAHeightEvent) error {
472473 return err
473474 }
474475
476+ // Verify forced inclusion transactions if configured
477+ if err := s .verifyForcedInclusionTxs (currentState , data ); err != nil {
478+ s .logger .Error ().Err (err ).Uint64 ("height" , nextHeight ).Msg ("forced inclusion verification failed" )
479+ // TODO(@julienrbrt): Eventually halt the syncer and request the node to be started using the based sequencer.
480+ }
481+
475482 // Apply block
476483 newState , err := s .applyBlock (header .Header , data , currentState )
477484 if err != nil {
@@ -589,6 +596,62 @@ func (s *Syncer) validateBlock(currState types.State, data *types.Data, header *
589596 return nil
590597}
591598
599+ // verifyForcedInclusionTxs verifies that all forced inclusion transactions from DA are included in the block
600+ func (s * Syncer ) verifyForcedInclusionTxs (currentState types.State , data * types.Data ) error {
601+ if s .daRetriever == nil {
602+ return nil
603+ }
604+
605+ // Retrieve forced inclusion transactions from DA
606+ forcedIncludedTxsEvent , err := s .daRetriever .RetrieveForcedIncludedTxsFromDA (s .ctx , currentState .DAHeight )
607+ if err != nil {
608+ if errors .Is (err , ErrForceInclusionNotConfigured ) {
609+ s .logger .Debug ().Msg ("forced inclusion namespace not configured, skipping verification" )
610+ return nil
611+ }
612+
613+ return fmt .Errorf ("failed to retrieve forced included txs from DA: %w" , err )
614+ }
615+
616+ // If no forced inclusion transactions found, nothing to verify
617+ if len (forcedIncludedTxsEvent .Txs ) == 0 {
618+ s .logger .Debug ().Uint64 ("da_height" , currentState .DAHeight ).Msg ("no forced inclusion transactions to verify" )
619+ return nil
620+ }
621+
622+ blockTxMap := make (map [string ]bool )
623+ for _ , tx := range data .Txs {
624+ blockTxMap [string (tx )] = true
625+ }
626+
627+ // Check if all forced inclusion transactions are present in the block
628+ var missingTxs [][]byte
629+ for _ , forcedTx := range forcedIncludedTxsEvent .Txs {
630+ if ! blockTxMap [string (forcedTx )] {
631+ missingTxs = append (missingTxs , forcedTx )
632+ }
633+ }
634+
635+ if len (missingTxs ) > 0 {
636+ s .logger .Error ().
637+ Uint64 ("height" , data .Height ()).
638+ Uint64 ("da_height" , currentState .DAHeight ).
639+ Uint64 ("da_epoch_start" , forcedIncludedTxsEvent .StartDaHeight ).
640+ Uint64 ("da_epoch_end" , forcedIncludedTxsEvent .EndDaHeight ).
641+ Int ("missing_count" , len (missingTxs )).
642+ Int ("total_forced" , len (forcedIncludedTxsEvent .Txs )).
643+ Msg ("SEQUENCER IS MALICIOUS: forced inclusion transactions missing from block" )
644+ return fmt .Errorf ("sequencer is malicious: %d forced inclusion transactions not included in block" , len (missingTxs ))
645+ }
646+
647+ s .logger .Debug ().
648+ Uint64 ("height" , data .Height ()).
649+ Int ("forced_txs" , len (forcedIncludedTxsEvent .Txs )).
650+ Msg ("all forced inclusion transactions verified in block" )
651+
652+ return nil
653+ }
654+
592655// sendCriticalError sends a critical error to the error channel without blocking
593656func (s * Syncer ) sendCriticalError (err error ) {
594657 if s .errorCh != nil {
0 commit comments