Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions block/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@ func NewAggregatorComponents(
logger,
errorCh,
)
// Set sequencer on submitter so it can initialize sequencer DA height from genesis inclusion
submitter.SetSequencer(sequencer)

return &Components{
Executor: executor,
Expand Down
18 changes: 9 additions & 9 deletions block/internal/da/forced_inclusion_retriever.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ var ErrForceInclusionNotConfigured = errors.New("forced inclusion namespace not

// ForcedInclusionRetriever handles retrieval of forced inclusion transactions from DA.
type ForcedInclusionRetriever struct {
client Client
genesis genesis.Genesis
logger zerolog.Logger
daEpochSize uint64
client Client
logger zerolog.Logger
daEpochSize uint64
daStartHeight uint64
}

// ForcedInclusionEvent contains forced inclusion transactions retrieved from DA.
Expand All @@ -39,10 +39,10 @@ func NewForcedInclusionRetriever(
logger zerolog.Logger,
) *ForcedInclusionRetriever {
return &ForcedInclusionRetriever{
client: client,
genesis: genesis,
logger: logger.With().Str("component", "forced_inclusion_retriever").Logger(),
daEpochSize: genesis.DAEpochForcedInclusion,
client: client,
daStartHeight: genesis.DAStartHeight, // TODO: this should be genesis da start height (for full nodes) or store metadata da start height (for sequencers)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

As the TODO comment highlights, daStartHeight is initialized from genesis.DAStartHeight and is never updated. For sequencers, this value should come from the store after it's determined and persisted by the submitter. Using a stale or incorrect daStartHeight will lead to incorrect epoch calculations in RetrieveForcedIncludedTxs, which can break the forced transaction inclusion mechanism.

To fix this, ForcedInclusionRetriever needs a way to get the correct genesis DA height. One approach is to give it access to the store so it can read the GenesisDAHeightKey metadata value.

For example, you could pass a store.Store to NewForcedInclusionRetriever and then in RetrieveForcedIncludedTxs you could do something like this:

	startHeight := r.daStartHeight // Fallback to genesis value
	genesisDAHeightBytes, err := r.store.GetMetadata(ctx, store.GenesisDAHeightKey)
	if err == nil {
		startHeight = binary.LittleEndian.Uint64(genesisDAHeightBytes)
	} else if !errors.Is(err, datastore.ErrNotFound) {
		r.logger.Warn().Err(err).Msg("failed to get genesis DA height from store, using default from genesis")
	}

	epochStart, epochEnd, currentEpochNumber := types.CalculateEpochBoundaries(daHeight, startHeight, r.daEpochSize)

This would ensure the correct daStartHeight is used for epoch calculations once it becomes available.

logger: logger.With().Str("component", "forced_inclusion_retriever").Logger(),
daEpochSize: genesis.DAEpochForcedInclusion,
}
}

Expand All @@ -53,7 +53,7 @@ func (r *ForcedInclusionRetriever) RetrieveForcedIncludedTxs(ctx context.Context
return nil, ErrForceInclusionNotConfigured
}

epochStart, epochEnd, currentEpochNumber := types.CalculateEpochBoundaries(daHeight, r.genesis.DAStartHeight, r.daEpochSize)
epochStart, epochEnd, currentEpochNumber := types.CalculateEpochBoundaries(daHeight, r.daStartHeight /* this should be fetch from store once */, r.daEpochSize)

if daHeight != epochEnd {
r.logger.Debug().
Expand Down
13 changes: 11 additions & 2 deletions block/internal/executing/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,21 @@ func (e *Executor) initializeState() error {
LastBlockHeight: e.genesis.InitialHeight - 1,
LastBlockTime: e.genesis.StartTime,
AppHash: stateRoot,
DAHeight: e.genesis.DAStartHeight,
// DA start height is usually 0 at InitChain unless it is a re-genesis.
// The sequencer does not know at which DA block its first block will be included.
DAHeight: e.genesis.DAStartHeight,
}
}

e.setLastState(state)
e.sequencer.SetDAHeight(state.DAHeight)
// Defer setting sequencer DA height at genesis. At chain genesis there are no
// included DA blocks yet, so the sequencer shouldn't be updated with the
// state's DA height until we've produced/observed at least the first included
// block. Only set the sequencer DA height when the chain has progressed past
// the initial genesis height.
if state.LastBlockHeight >= e.genesis.InitialHeight {
e.sequencer.SetDAHeight(state.DAHeight)
}

// Initialize store height using batch for atomicity
batch, err := e.store.NewBatch(e.ctx)
Expand Down
23 changes: 19 additions & 4 deletions block/internal/submitting/submitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/evstack/ev-node/block/internal/cache"
"github.com/evstack/ev-node/block/internal/common"
coreexecutor "github.com/evstack/ev-node/core/execution"
coresequencer "github.com/evstack/ev-node/core/sequencer"
"github.com/evstack/ev-node/pkg/config"
"github.com/evstack/ev-node/pkg/genesis"
"github.com/evstack/ev-node/pkg/signer"
Expand All @@ -31,10 +32,11 @@ type daSubmitterAPI interface {
// Submitter handles DA submission and inclusion processing for both sync and aggregator nodes
type Submitter struct {
// Core components
store store.Store
exec coreexecutor.Executor
config config.Config
genesis genesis.Genesis
store store.Store
exec coreexecutor.Executor
sequencer coresequencer.Sequencer
config config.Config
genesis genesis.Genesis

// Shared components
cache cache.Manager
Expand Down Expand Up @@ -93,6 +95,13 @@ func NewSubmitter(
}
}

// SetSequencer assigns the sequencer instance to the submitter.
// This allows the submitter to update the sequencer's DA height when the first
// DA inclusion (genesis) is observed.
func (s *Submitter) SetSequencer(seq coresequencer.Sequencer) {
s.sequencer = seq
}

// Start begins the submitting component
func (s *Submitter) Start(ctx context.Context) error {
s.ctx, s.cancel = context.WithCancel(ctx)
Expand Down Expand Up @@ -364,6 +373,12 @@ func (s *Submitter) setSequencerHeightToDAHeight(ctx context.Context, height uin
if err := s.store.SetMetadata(ctx, store.GenesisDAHeightKey, genesisDAIncludedHeightBytes); err != nil {
return err
}

// the sequencer will process DA epochs from this height.
if s.sequencer != nil {
s.sequencer.SetDAHeight(genesisDAIncludedHeight)
s.logger.Debug().Uint64("genesis_da_height", genesisDAIncludedHeight).Msg("initialized sequencer DA height from persisted genesis DA height")
}
}

return nil
Expand Down
4 changes: 3 additions & 1 deletion sequencers/based/sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ func NewBasedSequencer(
logger: logger.With().Str("component", "based_sequencer").Logger(),
checkpointStore: seqcommon.NewCheckpointStore(db, ds.NewKey("/based/checkpoint")),
}
bs.SetDAHeight(genesis.DAStartHeight) // will be overridden by the executor

// will be overridden by the executor or submitter (at genesis)
bs.SetDAHeight(genesis.DAStartHeight)

// Load checkpoint from DB, or initialize if none exists
loadCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
Expand Down
5 changes: 4 additions & 1 deletion sequencers/single/sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ func NewSequencer(
fiRetriever: fiRetriever,
checkpointStore: seqcommon.NewCheckpointStore(db, ds.NewKey("/single/checkpoint")),
}
s.SetDAHeight(genesis.DAStartHeight) // will be overridden by the executor
// will be overridden by the executor or submitter (at genesis)
// during genesis time, the sequencer will fetch unnecessary heights from DA genesis
// this is kept on purpose as some DAs (like local-da), do start at genesis.
s.SetDAHeight(genesis.DAStartHeight)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The comment on this line states that s.SetDAHeight will be overridden. While this is true, the current implementation of SetDAHeight is not sufficient to achieve the goal of this PR.

The SetDAHeight method only updates an atomic c.daHeight value. However, the sequencer's fetching logic in GetNextBatch is driven by c.checkpoint.DAHeight. The checkpoint is not updated by SetDAHeight, so when the submitter calls SetDAHeight with the correct genesis DA height, the sequencer will not start fetching from that height. It will continue using the stale height from the checkpoint.

To fix this, SetDAHeight must also update c.checkpoint.DAHeight and persist it. Here is a suggested implementation:

func (c *Sequencer) SetDAHeight(height uint64) {
	c.daHeight.Store(height)

	// If checkpoint is not initialized yet, we can't update it.
	// It will be initialized with the stored daHeight later.
	if c.checkpoint == nil {
		return
	}

	// Also update the checkpoint to ensure the sequencer fetches from the new height.
	if height > c.checkpoint.DAHeight {
		c.logger.Info().
			Uint64("old_da_height", c.checkpoint.DAHeight).
			Uint64("new_da_height", height).
			Msg("updating sequencer DA height checkpoint")
		c.checkpoint.DAHeight = height
		c.checkpoint.TxIndex = 0
		// Persisting the checkpoint here ensures that even after a restart,
		// the sequencer will start from the correct DA height.
		// A background context is acceptable here as this is a critical state update.
		if err := c.checkpointStore.Save(context.Background(), c.checkpoint); err != nil {
			c.logger.Error().Err(err).Msg("failed to save updated checkpoint in SetDAHeight")
		}
	}
}


loadCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
Expand Down
Loading