Skip to content

Commit c93fd9c

Browse files
committed
catch up fixes
1 parent 2234bcc commit c93fd9c

2 files changed

Lines changed: 51 additions & 1 deletion

File tree

pkg/sequencers/based/sequencer.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
datypes "github.com/evstack/ev-node/pkg/da/types"
1818
"github.com/evstack/ev-node/pkg/genesis"
1919
seqcommon "github.com/evstack/ev-node/pkg/sequencers/common"
20+
"github.com/evstack/ev-node/pkg/store"
2021
)
2122

2223
var _ coresequencer.Sequencer = (*BasedSequencer)(nil)
@@ -39,6 +40,10 @@ type BasedSequencer struct {
3940
currentDAEndTime time.Time
4041
// Total number of transactions in the current DA epoch (used for timestamp jitter)
4142
currentEpochTxCount uint64
43+
// lastTimestamp is the floor for timestamps to guarantee monotonicity
44+
// after a restart on a node that already had blocks produced with wall-clock time.
45+
// Initialised from the last block time in the store at construction.
46+
lastTimestamp time.Time
4247
}
4348

4449
// NewBasedSequencer creates a new based sequencer instance
@@ -56,6 +61,20 @@ func NewBasedSequencer(
5661
executor: executor,
5762
currentDAEndTime: genesis.StartTime,
5863
}
64+
65+
// Read the last block time from the store so that timestamps are
66+
// guaranteed to be strictly after any previously produced or synced block.
67+
// This handles the case where a node that already had blocks (produced with
68+
// wall-clock time) restarts as a based sequencer.
69+
initCtx, initCancel := context.WithTimeout(context.Background(), 10*time.Second)
70+
defer initCancel()
71+
s := store.New(store.NewEvNodeKVStore(db))
72+
if state, err := s.GetState(initCtx); err == nil && !state.LastBlockTime.IsZero() {
73+
bs.lastTimestamp = state.LastBlockTime
74+
bs.logger.Debug().
75+
Time("last_block_time", state.LastBlockTime).
76+
Msg("initialized timestamp floor from last block time")
77+
}
5978
// based sequencers need community consensus about the da start height given no submission are done
6079
bs.SetDAHeight(genesis.DAStartHeight)
6180

@@ -194,6 +213,14 @@ doneProcessing:
194213
epochStart := s.currentDAEndTime.Add(-time.Duration(s.currentEpochTxCount) * time.Millisecond)
195214
timestamp := epochStart.Add(time.Duration(txIndexForTimestamp) * time.Millisecond)
196215

216+
// Clamp: the DA-derived timestamp may predate blocks that were
217+
// produced or synced with wall-clock time before the node restarted
218+
// as a based sequencer. Ensure strict monotonicity.
219+
if !s.lastTimestamp.IsZero() && !timestamp.After(s.lastTimestamp) {
220+
timestamp = s.lastTimestamp.Add(time.Millisecond)
221+
}
222+
s.lastTimestamp = timestamp
223+
197224
return &coresequencer.GetNextBatchResponse{
198225
Batch: &coresequencer.Batch{Transactions: validTxs},
199226
Timestamp: timestamp,

pkg/sequencers/single/sequencer.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ type Sequencer struct {
6666
currentDAEndTime time.Time
6767
// currentEpochTxCount is the total number of txs in the current DA epoch (used for timestamp jitter)
6868
currentEpochTxCount uint64
69+
// lastCatchUpTimestamp is the floor for catch-up timestamps to guarantee
70+
// monotonicity after a restart. Initialised from the last block time in
71+
// the store when catch-up mode is entered.
72+
lastCatchUpTimestamp time.Time
6973
}
7074

7175
// NewSequencer creates a new Single Sequencer
@@ -357,6 +361,14 @@ func (c *Sequencer) GetNextBatch(ctx context.Context, req coresequencer.GetNextB
357361
if c.catchUpState.Load() == catchUpInProgress && !c.currentDAEndTime.IsZero() {
358362
epochStart := c.currentDAEndTime.Add(-time.Duration(c.currentEpochTxCount) * time.Millisecond)
359363
timestamp = epochStart.Add(time.Duration(txIndexForTimestamp) * time.Millisecond)
364+
365+
// Clamp: the DA-derived timestamp may predate blocks that were
366+
// produced with time.Now() before the sequencer was restarted.
367+
// Ensure strict monotonicity relative to the last produced block.
368+
if !c.lastCatchUpTimestamp.IsZero() && !timestamp.After(c.lastCatchUpTimestamp) {
369+
timestamp = c.lastCatchUpTimestamp.Add(time.Millisecond)
370+
}
371+
c.lastCatchUpTimestamp = timestamp
360372
}
361373

362374
return &coresequencer.GetNextBatchResponse{
@@ -534,7 +546,18 @@ func (c *Sequencer) updateCatchUpState(ctx context.Context) {
534546
return
535547
}
536548

537-
// More than one epoch behind - enter catch-up mode
549+
// More than one epoch behind - enter catch-up mode.
550+
// Read the last block time from the store so that catch-up timestamps
551+
// are guaranteed to be strictly after any previously produced block.
552+
s := store.New(store.NewEvNodeKVStore(c.db))
553+
state, err := s.GetState(ctx)
554+
if err == nil && !state.LastBlockTime.IsZero() {
555+
c.lastCatchUpTimestamp = state.LastBlockTime
556+
c.logger.Debug().
557+
Time("last_block_time", state.LastBlockTime).
558+
Msg("initialized catch-up timestamp floor from last block time")
559+
}
560+
538561
c.catchUpState.Store(catchUpInProgress)
539562
c.logger.Warn().
540563
Uint64("checkpoint_da_height", currentDAHeight).

0 commit comments

Comments
 (0)