Skip to content

Commit fcf4b08

Browse files
committed
add smoothing checks
1 parent a928c93 commit fcf4b08

File tree

2 files changed

+448
-35
lines changed

2 files changed

+448
-35
lines changed

block/internal/syncing/syncer.go

Lines changed: 109 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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
7988
func 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.
669684
func (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

Comments
 (0)