From 1007e33aa87540a0b981d0bf1980bd26fb3abd2b Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:32:55 +0100 Subject: [PATCH 01/26] basic prunning --- block/internal/executing/executor.go | 17 ++++++ pkg/config/config.go | 11 ++++ pkg/config/defaults.go | 3 ++ pkg/store/keys.go | 6 +++ pkg/store/store.go | 79 ++++++++++++++++++++++++++++ pkg/store/store_test.go | 51 ++++++++++++++++++ pkg/store/types.go | 19 ++++++- 7 files changed, 185 insertions(+), 1 deletion(-) diff --git a/block/internal/executing/executor.go b/block/internal/executing/executor.go index bf1b44b6cb..341c5ec289 100644 --- a/block/internal/executing/executor.go +++ b/block/internal/executing/executor.go @@ -546,6 +546,23 @@ func (e *Executor) ProduceBlock(ctx context.Context) error { // Update in-memory state after successful commit e.setLastState(newState) + // Run height-based pruning of stored block data if enabled. This is a + // best-effort background maintenance step and should not cause block + // production to fail, but it does run in the critical path and may add + // some latency when large ranges are pruned. + if e.config.Node.PruningEnabled && e.config.Node.PruningKeepRecent > 0 && e.config.Node.PruningInterval > 0 { + if newHeight%e.config.Node.PruningInterval == 0 { + // Compute the prune floor: all heights <= targetHeight are candidates + // for pruning of header/data/signature/index entries. + if newHeight > e.config.Node.PruningKeepRecent { + targetHeight := newHeight - e.config.Node.PruningKeepRecent + if err := e.store.PruneBlocks(e.ctx, targetHeight); err != nil { + e.logger.Error().Err(err).Uint64("target_height", targetHeight).Msg("failed to prune old block data") + } + } + } + } + // broadcast header and data to P2P network g, broadcastCtx := errgroup.WithContext(e.ctx) g.Go(func() error { diff --git a/pkg/config/config.go b/pkg/config/config.go index e03a277ce8..36354a9090 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -261,6 +261,13 @@ type NodeConfig struct { // Readiness / health configuration ReadinessWindowSeconds uint64 `mapstructure:"readiness_window_seconds" yaml:"readiness_window_seconds" comment:"Time window in seconds used to calculate ReadinessMaxBlocksBehind based on block time. Default: 15 seconds."` ReadinessMaxBlocksBehind uint64 `mapstructure:"readiness_max_blocks_behind" yaml:"readiness_max_blocks_behind" comment:"How many blocks behind best-known head the node can be and still be considered ready. 0 means must be exactly at head."` + + // Pruning configuration + // When enabled, the node will periodically prune old block data (headers, data, + // signatures, and hash index) from the local store while keeping recent history. + PruningEnabled bool `mapstructure:"pruning_enabled" yaml:"pruning_enabled" comment:"Enable height-based pruning of stored block data. When disabled, all blocks are kept (archive mode)."` + PruningKeepRecent uint64 `mapstructure:"pruning_keep_recent" yaml:"pruning_keep_recent" comment:"Number of most recent blocks to retain. Older blocks will have their header/data/signature removed from the local store. 0 means keep all blocks."` + PruningInterval uint64 `mapstructure:"pruning_interval" yaml:"pruning_interval" comment:"Run pruning every N blocks. Must be >= 1 when pruning is enabled."` } // LogConfig contains all logging configuration parameters @@ -436,6 +443,10 @@ func AddFlags(cmd *cobra.Command) { cmd.Flags().Uint64(FlagReadinessWindowSeconds, def.Node.ReadinessWindowSeconds, "time window in seconds for calculating readiness threshold based on block time (default: 15s)") cmd.Flags().Uint64(FlagReadinessMaxBlocksBehind, def.Node.ReadinessMaxBlocksBehind, "how many blocks behind best-known head the node can be and still be considered ready (0 = must be at head)") cmd.Flags().Duration(FlagScrapeInterval, def.Node.ScrapeInterval.Duration, "interval at which the reaper polls the execution layer for new transactions") + // Pruning configuration flags + cmd.Flags().Bool(FlagPrefixEvnode+"node.pruning_enabled", def.Node.PruningEnabled, "enable height-based pruning of stored block data (headers, data, signatures, index)") + cmd.Flags().Uint64(FlagPrefixEvnode+"node.pruning_keep_recent", def.Node.PruningKeepRecent, "number of most recent blocks to retain when pruning is enabled (0 = keep all)") + cmd.Flags().Uint64(FlagPrefixEvnode+"node.pruning_interval", def.Node.PruningInterval, "run pruning every N blocks (must be >= 1 when pruning is enabled)") // Data Availability configuration flags cmd.Flags().String(FlagDAAddress, def.DA.Address, "DA address (host:port)") diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 0de2f4bc27..ee2cbfbeec 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -69,6 +69,9 @@ func DefaultConfig() Config { ReadinessWindowSeconds: defaultReadinessWindowSeconds, ReadinessMaxBlocksBehind: calculateReadinessMaxBlocksBehind(defaultBlockTime.Duration, defaultReadinessWindowSeconds), ScrapeInterval: DurationWrapper{1 * time.Second}, + PruningEnabled: false, + PruningKeepRecent: 0, + PruningInterval: 0, }, DA: DAConfig{ Address: "http://localhost:7980", diff --git a/pkg/store/keys.go b/pkg/store/keys.go index dd989c0e82..ff96fd8955 100644 --- a/pkg/store/keys.go +++ b/pkg/store/keys.go @@ -25,6 +25,12 @@ const ( // LastSubmittedHeaderHeightKey is the key used for persisting the last submitted header height in store. LastSubmittedHeaderHeightKey = "last-submitted-header-height" + // LastPrunedBlockHeightKey is the metadata key used for persisting the last + // pruned block height in the store. All block data (header, data, + // signature, and hash index) for heights <= this value are considered + // pruned and may be missing from the store. + LastPrunedBlockHeightKey = "last-pruned-block-height" + headerPrefix = "h" dataPrefix = "d" signaturePrefix = "c" diff --git a/pkg/store/store.go b/pkg/store/store.go index a821e6a8d4..9384061ba7 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -296,6 +296,85 @@ func (s *DefaultStore) Rollback(ctx context.Context, height uint64, aggregator b return nil } +// PruneBlocks removes block data (header, data, signature, and hash index) +// up to and including the given height from the store. It does not modify +// the current chain height or any state snapshots. +// +// This method is intended for long-term storage reduction and is safe to +// call repeatedly with the same or increasing heights. +func (s *DefaultStore) PruneBlocks(ctx context.Context, height uint64) error { + batch, err := s.db.Batch(ctx) + if err != nil { + return fmt.Errorf("failed to create a new batch for pruning: %w", err) + } + + // Track the last successfully pruned height so we can resume across restarts. + var lastPruned uint64 + meta, err := s.GetMetadata(ctx, LastPrunedBlockHeightKey) + if err != nil { + if !errors.Is(err, ds.ErrNotFound) { + return fmt.Errorf("failed to get last pruned height: %w", err) + } + } else if len(meta) == heightLength { + lastPruned, err = decodeHeight(meta) + if err != nil { + return fmt.Errorf("failed to decode last pruned height: %w", err) + } + } + + // Nothing new to prune. + if height <= lastPruned { + return nil + } + + // Delete block data for heights in (lastPruned, height]. + for h := lastPruned + 1; h <= height; h++ { + // Get header blob to compute the hash index key. If header is already + // missing (e.g. due to previous partial pruning), just skip this height. + headerBlob, err := s.db.Get(ctx, ds.NewKey(getHeaderKey(h))) + if err != nil { + if errors.Is(err, ds.ErrNotFound) { + continue + } + return fmt.Errorf("failed to get header at height %d during pruning: %w", h, err) + } + + if err := batch.Delete(ctx, ds.NewKey(getHeaderKey(h))); err != nil { + return fmt.Errorf("failed to delete header at height %d during pruning: %w", h, err) + } + + if err := batch.Delete(ctx, ds.NewKey(getDataKey(h))); err != nil { + if !errors.Is(err, ds.ErrNotFound) { + return fmt.Errorf("failed to delete data at height %d during pruning: %w", h, err) + } + } + + if err := batch.Delete(ctx, ds.NewKey(getSignatureKey(h))); err != nil { + if !errors.Is(err, ds.ErrNotFound) { + return fmt.Errorf("failed to delete signature at height %d during pruning: %w", h, err) + } + } + + headerHash := sha256.Sum256(headerBlob) + if err := batch.Delete(ctx, ds.NewKey(getIndexKey(headerHash[:]))); err != nil { + if !errors.Is(err, ds.ErrNotFound) { + return fmt.Errorf("failed to delete index for height %d during pruning: %w", h, err) + } + } + } + + // Persist the updated last pruned height. + if err := batch.Put(ctx, ds.NewKey(getMetaKey(LastPrunedBlockHeightKey)), encodeHeight(height)); err != nil { + return fmt.Errorf("failed to update last pruned height: %w", err) + } + + if err := batch.Commit(ctx); err != nil { + return fmt.Errorf("failed to commit pruning batch: %w", err) + } + + return nil +} + const heightLength = 8 func encodeHeight(height uint64) []byte { diff --git a/pkg/store/store_test.go b/pkg/store/store_test.go index 8636a0ad0d..b9a796788d 100644 --- a/pkg/store/store_test.go +++ b/pkg/store/store_test.go @@ -734,6 +734,57 @@ func TestRollback(t *testing.T) { require.Equal(rollbackToHeight, state.LastBlockHeight) } +func TestPruneBlocks_RemovesOldBlockDataOnly(t *testing.T) { + t.Parallel() + + ctx := context.Background() + ds, err := NewTestInMemoryKVStore() + require.NoError(t, err) + + s := New(ds).(*DefaultStore) + + // create and store a few blocks with headers, data, signatures and state + batch, err := s.NewBatch(ctx) + require.NoError(t, err) + + var lastState types.State + for h := uint64(1); h <= 5; h++ { + header := &types.SignedHeader{Header: types.Header{BaseHeader: types.BaseHeader{Height: h}}} + data := &types.Data{} + sig := types.Signature([]byte{byte(h)}) + + require.NoError(t, batch.SaveBlockData(header, data, &sig)) + + // fake state snapshot per height + lastState = types.State{LastBlockHeight: h} + require.NoError(t, batch.UpdateState(lastState)) + } + require.NoError(t, batch.SetHeight(5)) + require.NoError(t, batch.Commit()) + + // prune everything up to height 3 + require.NoError(t, s.PruneBlocks(ctx, 3)) + + // old block data should be gone + for h := uint64(1); h <= 3; h++ { + _, _, err := s.GetBlockData(ctx, h) + assert.Error(t, err, "expected block data at height %d to be pruned", h) + } + + // recent block data should remain + for h := uint64(4); h <= 5; h++ { + _, _, err := s.GetBlockData(ctx, h) + assert.NoError(t, err, "expected block data at height %d to be kept", h) + } + + // state snapshots are not pruned by PruneBlocks + for h := uint64(1); h <= 5; h++ { + st, err := s.GetStateAtHeight(ctx, h) + assert.NoError(t, err, "expected state at height %d to remain", h) + assert.Equal(t, h, st.LastBlockHeight) + } +} + // TestRollbackToSameHeight verifies that rollback to same height is a no-op func TestRollbackToSameHeight(t *testing.T) { t.Parallel() diff --git a/pkg/store/types.go b/pkg/store/types.go index fa1ecdc92c..fe36e0be53 100644 --- a/pkg/store/types.go +++ b/pkg/store/types.go @@ -30,9 +30,15 @@ type Batch interface { } // Store is minimal interface for storing and retrieving blocks, commits and state. +// +// It is composed from three concerns: +// - Reader: read access to blocks, state, and metadata +// - Rollback: consensus rollback logic (used for chain reorgs / recovery) +// - Pruner: long-term height-based pruning of historical block data type Store interface { - Rollback Reader + Rollback + Pruner // SetMetadata saves arbitrary value in the store. // @@ -78,3 +84,14 @@ type Rollback interface { // Aggregator is used to determine if the rollback is performed on the aggregator node. Rollback(ctx context.Context, height uint64, aggregator bool) error } + +// Pruner provides long-term, height-based pruning of historical block data. +// +// Implementations SHOULD be idempotent and safe to call multiple times for +// the same or increasing target heights. +type Pruner interface { + // PruneBlocks removes block data (header, data, signature, and hash index) + // up to and including the given height from the store, without modifying + // state snapshots or the current chain height. + PruneBlocks(ctx context.Context, height uint64) error +} From 885acd76eecd55b2e695bbef900b9dbf8a22f46c Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:10:28 +0100 Subject: [PATCH 02/26] prune metadata from ev-node store --- pkg/store/store.go | 12 ++++++++++++ pkg/store/store_test.go | 30 +++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/pkg/store/store.go b/pkg/store/store.go index 9384061ba7..5b818bd803 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -355,6 +355,18 @@ func (s *DefaultStore) PruneBlocks(ctx context.Context, height uint64) error { } } + // Delete per-height DA metadata associated with this height, if any. + if err := batch.Delete(ctx, ds.NewKey(getMetaKey(GetHeightToDAHeightHeaderKey(h)))); err != nil { + if !errors.Is(err, ds.ErrNotFound) { + return fmt.Errorf("failed to delete header DA height metadata at height %d during pruning: %w", h, err) + } + } + if err := batch.Delete(ctx, ds.NewKey(getMetaKey(GetHeightToDAHeightDataKey(h)))); err != nil { + if !errors.Is(err, ds.ErrNotFound) { + return fmt.Errorf("failed to delete data DA height metadata at height %d during pruning: %w", h, err) + } + } + headerHash := sha256.Sum256(headerBlob) if err := batch.Delete(ctx, ds.NewKey(getIndexKey(headerHash[:]))); err != nil { if !errors.Is(err, ds.ErrNotFound) { diff --git a/pkg/store/store_test.go b/pkg/store/store_test.go index b9a796788d..5ba5eecfad 100644 --- a/pkg/store/store_test.go +++ b/pkg/store/store_test.go @@ -743,7 +743,7 @@ func TestPruneBlocks_RemovesOldBlockDataOnly(t *testing.T) { s := New(ds).(*DefaultStore) - // create and store a few blocks with headers, data, signatures and state + // create and store a few blocks with headers, data, signatures, state, and per-height DA metadata batch, err := s.NewBatch(ctx) require.NoError(t, err) @@ -758,6 +758,14 @@ func TestPruneBlocks_RemovesOldBlockDataOnly(t *testing.T) { // fake state snapshot per height lastState = types.State{LastBlockHeight: h} require.NoError(t, batch.UpdateState(lastState)) + + // store fake DA metadata per height + hDaKey := GetHeightToDAHeightHeaderKey(h) + dDaKey := GetHeightToDAHeightDataKey(h) + bz := make([]byte, 8) + binary.LittleEndian.PutUint64(bz, h+100) // arbitrary DA height + require.NoError(t, s.SetMetadata(ctx, hDaKey, bz)) + require.NoError(t, s.SetMetadata(ctx, dDaKey, bz)) } require.NoError(t, batch.SetHeight(5)) require.NoError(t, batch.Commit()) @@ -783,6 +791,26 @@ func TestPruneBlocks_RemovesOldBlockDataOnly(t *testing.T) { assert.NoError(t, err, "expected state at height %d to remain", h) assert.Equal(t, h, st.LastBlockHeight) } + + // per-height DA metadata for pruned heights should be gone + for h := uint64(1); h <= 3; h++ { + hDaKey := GetHeightToDAHeightHeaderKey(h) + dDaKey := GetHeightToDAHeightDataKey(h) + _, err := s.GetMetadata(ctx, hDaKey) + assert.Error(t, err, "expected header DA metadata at height %d to be pruned", h) + _, err = s.GetMetadata(ctx, dDaKey) + assert.Error(t, err, "expected data DA metadata at height %d to be pruned", h) + } + + // per-height DA metadata for unpruned heights should remain + for h := uint64(4); h <= 5; h++ { + hDaKey := GetHeightToDAHeightHeaderKey(h) + dDaKey := GetHeightToDAHeightDataKey(h) + _, err := s.GetMetadata(ctx, hDaKey) + assert.NoError(t, err, "expected header DA metadata at height %d to remain", h) + _, err = s.GetMetadata(ctx, dDaKey) + assert.NoError(t, err, "expected data DA metadata at height %d to remain", h) + } } // TestRollbackToSameHeight verifies that rollback to same height is a no-op From 3abc2e5e8921ab50358025317854863246f38647 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:27:04 +0100 Subject: [PATCH 03/26] wiring prunning config to go-header --- pkg/sync/sync_service.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/sync/sync_service.go b/pkg/sync/sync_service.go index 8567e79764..c9e26fd31e 100644 --- a/pkg/sync/sync_service.go +++ b/pkg/sync/sync_service.go @@ -189,11 +189,21 @@ func (syncService *SyncService[H]) Start(ctx context.Context) error { } // create syncer, must be before initFromP2PWithRetry which calls startSyncer. + syncOpts := []goheadersync.Option{goheadersync.WithBlockTime(syncService.conf.Node.BlockTime.Duration)} + // Map ev-node pruning configuration to go-header's pruning window: we approximate + // "keep N recent heights" as "retain headers for N * blockTime". + if syncService.conf.Node.PruningEnabled && syncService.conf.Node.PruningKeepRecent > 0 { + pruningWindow := syncService.conf.Node.BlockTime.Duration * time.Duration(syncService.conf.Node.PruningKeepRecent) + // Only set a pruning window if the computed duration is positive. + if pruningWindow > 0 { + syncOpts = append(syncOpts, goheadersync.WithPruningWindow(pruningWindow)) + } + } if syncService.syncer, err = newSyncer( syncService.ex, syncService.store, syncService.sub, - []goheadersync.Option{goheadersync.WithBlockTime(syncService.conf.Node.BlockTime.Duration)}, + syncOpts, ); err != nil { return fmt.Errorf("failed to create syncer: %w", err) } @@ -467,7 +477,10 @@ func newSyncer[H header.Header[H]]( opts = append(opts, goheadersync.WithMetrics(), +<<<<<<< HEAD goheadersync.WithPruningWindow(ninetyNineYears), // pruning window not relevant, because of the store wrapper. +======= +>>>>>>> 3b9a0f70 (wiring prunning config to go-header) goheadersync.WithTrustingPeriod(ninetyNineYears), ) return goheadersync.NewSyncer(ex, store, sub, opts...) From 9c4839e2c1b05062d2fb27b0109a6882ec84cb5a Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Mon, 26 Jan 2026 12:42:55 +0100 Subject: [PATCH 04/26] prune evm exec store --- block/internal/executing/executor.go | 11 ++++ core/execution/execution.go | 13 ++++ execution/evm/execution.go | 17 +++++ execution/evm/store.go | 53 +++++++++++++++ execution/evm/store_test.go | 99 ++++++++++++++++++++++++++++ 5 files changed, 193 insertions(+) create mode 100644 execution/evm/store_test.go diff --git a/block/internal/executing/executor.go b/block/internal/executing/executor.go index 341c5ec289..426afdcf06 100644 --- a/block/internal/executing/executor.go +++ b/block/internal/executing/executor.go @@ -559,6 +559,17 @@ func (e *Executor) ProduceBlock(ctx context.Context) error { if err := e.store.PruneBlocks(e.ctx, targetHeight); err != nil { e.logger.Error().Err(err).Uint64("target_height", targetHeight).Msg("failed to prune old block data") } + + // If the execution client exposes execution-metadata pruning, + // prune ExecMeta using the same target height. This keeps EVM + // execution metadata aligned with ev-node's block store pruning + // while remaining a no-op for execution environments that don't + // implement ExecMetaPruner (e.g. ABCI-based executors). + if pruner, ok := e.exec.(coreexecutor.ExecMetaPruner); ok { + if err := pruner.PruneExecMeta(e.ctx, targetHeight); err != nil { + e.logger.Error().Err(err).Uint64("target_height", targetHeight).Msg("failed to prune execution metadata") + } + } } } } diff --git a/core/execution/execution.go b/core/execution/execution.go index 276da369e9..1f85e2068a 100644 --- a/core/execution/execution.go +++ b/core/execution/execution.go @@ -161,3 +161,16 @@ type Rollbackable interface { // Rollback resets the execution layer head to the specified height. Rollback(ctx context.Context, targetHeight uint64) error } + +// ExecMetaPruner is an optional interface that execution clients can implement +// to support height-based pruning of their execution metadata. This is used by +// EVM-based execution clients to keep ExecMeta consistent with ev-node's +// pruning window while remaining a no-op for execution environments that +// don't persist per-height metadata in ev-node's datastore. +type ExecMetaPruner interface { + // PruneExecMeta should delete execution metadata for all heights up to and + // including the given height. Implementations should be idempotent and track + // their own progress so that repeated calls with the same or decreasing + // heights are cheap no-ops. + PruneExecMeta(ctx context.Context, height uint64) error +} diff --git a/execution/evm/execution.go b/execution/evm/execution.go index 064c2c7496..795b8fa31e 100644 --- a/execution/evm/execution.go +++ b/execution/evm/execution.go @@ -65,6 +65,12 @@ var _ execution.HeightProvider = (*EngineClient)(nil) // Ensure EngineClient implements the execution.Rollbackable interface var _ execution.Rollbackable = (*EngineClient)(nil) +// Ensure EngineClient implements optional pruning interface when used with +// ev-node's height-based pruning. This enables coordinated pruning of EVM +// ExecMeta alongside ev-node's own block data pruning, while remaining a +// no-op for non-EVM execution environments. +var _ execution.ExecMetaPruner = (*EngineClient)(nil) + // validatePayloadStatus checks the payload status and returns appropriate errors. // It implements the Engine API specification's status handling: // - VALID: Operation succeeded, return nil @@ -265,6 +271,17 @@ func NewEngineExecutionClient( }, nil } +// PruneExecMeta implements execution.ExecMetaPruner by delegating to the +// underlying EVMStore. It is safe to call this multiple times with the same +// or increasing heights; the store tracks its own last-pruned height. +func (c *EngineClient) PruneExecMeta(ctx context.Context, height uint64) error { + if c.store == nil { + return nil + } + + return c.store.PruneExecMeta(ctx, height) +} + // SetLogger allows callers to attach a structured logger. func (c *EngineClient) SetLogger(l zerolog.Logger) { c.logger = l diff --git a/execution/evm/store.go b/execution/evm/store.go index af731ee7c6..51a3056b66 100644 --- a/execution/evm/store.go +++ b/execution/evm/store.go @@ -15,6 +15,11 @@ import ( // Store prefix for execution/evm data - keeps it isolated from other ev-node data const evmStorePrefix = "evm/" +// lastPrunedExecMetaKey is the datastore key used to track the highest +// execution height for which ExecMeta has been pruned. All ExecMeta entries +// for heights <= this value are considered pruned. +const lastPrunedExecMetaKey = evmStorePrefix + "last-pruned-execmeta-height" + // ExecMeta stages const ( ExecStageStarted = "started" @@ -140,6 +145,54 @@ func (s *EVMStore) SaveExecMeta(ctx context.Context, meta *ExecMeta) error { return nil } +// PruneExecMeta removes ExecMeta entries up to and including the given height. +// It is safe to call this multiple times with the same or increasing heights; +// previously pruned ranges will be skipped based on the last-pruned marker. +func (s *EVMStore) PruneExecMeta(ctx context.Context, height uint64) error { + // Load last pruned height, if any. + var lastPruned uint64 + data, err := s.db.Get(ctx, ds.NewKey(lastPrunedExecMetaKey)) + if err != nil { + if !errors.Is(err, ds.ErrNotFound) { + return fmt.Errorf("failed to get last pruned execmeta height: %w", err) + } + } else if len(data) == 8 { + lastPruned = binary.BigEndian.Uint64(data) + } + + // Nothing new to prune. + if height <= lastPruned { + return nil + } + + batch, err := s.db.Batch(ctx) + if err != nil { + return fmt.Errorf("failed to create batch for execmeta pruning: %w", err) + } + + for h := lastPruned + 1; h <= height; h++ { + key := execMetaKey(h) + if err := batch.Delete(ctx, key); err != nil { + if !errors.Is(err, ds.ErrNotFound) { + return fmt.Errorf("failed to delete exec meta at height %d: %w", h, err) + } + } + } + + // Persist updated last pruned height. + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf, height) + if err := batch.Put(ctx, ds.NewKey(lastPrunedExecMetaKey), buf); err != nil { + return fmt.Errorf("failed to update last pruned execmeta height: %w", err) + } + + if err := batch.Commit(ctx); err != nil { + return fmt.Errorf("failed to commit execmeta pruning batch: %w", err) + } + + return nil +} + // Sync ensures all pending writes are flushed to disk. func (s *EVMStore) Sync(ctx context.Context) error { return s.db.Sync(ctx, ds.NewKey(evmStorePrefix)) diff --git a/execution/evm/store_test.go b/execution/evm/store_test.go new file mode 100644 index 0000000000..64389701f9 --- /dev/null +++ b/execution/evm/store_test.go @@ -0,0 +1,99 @@ +package evm + +import ( + "context" + "encoding/binary" + "testing" + + ds "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + "github.com/stretchr/testify/require" +) + +// newTestDatastore creates an in-memory datastore for testing. +func newTestDatastore(t *testing.T) ds.Batching { + t.Helper() + // Wrap the in-memory MapDatastore to satisfy the Batching interface. + return dssync.MutexWrap(ds.NewMapDatastore()) +} + +func TestPruneExecMeta_PrunesUpToTargetHeight(t *testing.T) { + t.Parallel() + + ctx := context.Background() + db := newTestDatastore(t) + store := NewEVMStore(db) + + // Seed ExecMeta entries at heights 1..5 + for h := uint64(1); h <= 5; h++ { + meta := &ExecMeta{Height: h} + require.NoError(t, store.SaveExecMeta(ctx, meta)) + } + + // Sanity: all heights should be present + for h := uint64(1); h <= 5; h++ { + meta, err := store.GetExecMeta(ctx, h) + require.NoError(t, err) + require.NotNil(t, meta) + require.Equal(t, h, meta.Height) + } + + // Prune up to height 3 + require.NoError(t, store.PruneExecMeta(ctx, 3)) + + // Heights 1..3 should be gone + for h := uint64(1); h <= 3; h++ { + meta, err := store.GetExecMeta(ctx, h) + require.NoError(t, err) + require.Nil(t, meta) + } + + // Heights 4..5 should remain + for h := uint64(4); h <= 5; h++ { + meta, err := store.GetExecMeta(ctx, h) + require.NoError(t, err) + require.NotNil(t, meta) + } + + // Re-pruning with the same height should be a no-op + require.NoError(t, store.PruneExecMeta(ctx, 3)) +} + +func TestPruneExecMeta_TracksLastPrunedHeight(t *testing.T) { + t.Parallel() + + ctx := context.Background() + db := newTestDatastore(t) + store := NewEVMStore(db) + + // Seed ExecMeta entries at heights 1..5 + for h := uint64(1); h <= 5; h++ { + meta := &ExecMeta{Height: h} + require.NoError(t, store.SaveExecMeta(ctx, meta)) + } + + // First prune up to 2 + require.NoError(t, store.PruneExecMeta(ctx, 2)) + + // Then prune up to 4; heights 3..4 should be deleted in this run + require.NoError(t, store.PruneExecMeta(ctx, 4)) + + // Verify all heights 1..4 are gone, 5 remains + for h := uint64(1); h <= 4; h++ { + meta, err := store.GetExecMeta(ctx, h) + require.NoError(t, err) + require.Nil(t, meta) + } + + meta, err := store.GetExecMeta(ctx, 5) + require.NoError(t, err) + require.NotNil(t, meta) + require.Equal(t, uint64(5), meta.Height) + + // Ensure last-pruned marker is set to 4 + raw, err := db.Get(ctx, ds.NewKey(lastPrunedExecMetaKey)) + require.NoError(t, err) + require.Len(t, raw, 8) + last := binary.BigEndian.Uint64(raw) + require.Equal(t, uint64(4), last) +} From 54f7af5f7d7ef5c1e3309d2dbe9728749feae712 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:45:24 +0100 Subject: [PATCH 05/26] add parameters validation --- pkg/config/config.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pkg/config/config.go b/pkg/config/config.go index 36354a9090..7a14aaae0a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -383,6 +383,21 @@ func (c *Config) Validate() error { if err := c.Raft.Validate(); err != nil { return err } + + // Validate pruning configuration + if c.Node.PruningEnabled { + // When pruning is enabled, pruning_interval must be >= 1 + if c.Node.PruningInterval == 0 { + return fmt.Errorf("pruning_interval must be >= 1 when pruning is enabled") + } + + // When pruning is enabled, keeping 0 blocks is contradictory; use pruning_enabled=false + // for archive mode instead. + if c.Node.PruningKeepRecent == 0 { + return fmt.Errorf("pruning_keep_recent must be > 0 when pruning is enabled; use pruning_enabled=false to keep all blocks") + } + } + return nil } From bf83c4ec755f874c0510fa6d905f2d6e2496caf4 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:47:32 +0100 Subject: [PATCH 06/26] make error handling consistent --- pkg/store/store.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/store/store.go b/pkg/store/store.go index 5b818bd803..fa8a2bfd5a 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -340,7 +340,9 @@ func (s *DefaultStore) PruneBlocks(ctx context.Context, height uint64) error { } if err := batch.Delete(ctx, ds.NewKey(getHeaderKey(h))); err != nil { - return fmt.Errorf("failed to delete header at height %d during pruning: %w", h, err) + if !errors.Is(err, ds.ErrNotFound) { + return fmt.Errorf("failed to delete header at height %d during pruning: %w", h, err) + } } if err := batch.Delete(ctx, ds.NewKey(getDataKey(h))); err != nil { From 3cfb0d482b2bff5bc81b0e00b352a4f392e7f044 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:19:26 +0100 Subject: [PATCH 07/26] add method to tracedstore to respect interface --- pkg/store/tracing.go | 16 ++++++++++++++++ pkg/store/tracing_test.go | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/pkg/store/tracing.go b/pkg/store/tracing.go index 04baf748fd..004e0bd386 100644 --- a/pkg/store/tracing.go +++ b/pkg/store/tracing.go @@ -230,6 +230,22 @@ func (t *tracedStore) Rollback(ctx context.Context, height uint64, aggregator bo return nil } +func (t *tracedStore) PruneBlocks(ctx context.Context, height uint64) error { + ctx, span := t.tracer.Start(ctx, "Store.PruneBlocks", + trace.WithAttributes(attribute.Int64("height", int64(height))), + ) + defer span.End() + + err := t.inner.PruneBlocks(ctx, height) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + + return nil +} + func (t *tracedStore) Close() error { return t.inner.Close() } diff --git a/pkg/store/tracing_test.go b/pkg/store/tracing_test.go index 5c273f6613..6d7e520e1f 100644 --- a/pkg/store/tracing_test.go +++ b/pkg/store/tracing_test.go @@ -116,6 +116,12 @@ func (m *tracingMockStore) Rollback(ctx context.Context, height uint64, aggregat return nil } +func (m *tracingMockStore) PruneBlocks(ctx context.Context, height uint64) error { + // For tracing tests we don't need pruning behavior; just satisfy the Store + // interface. Specific pruning behavior is tested separately in store_test.go. + return nil +} + func (m *tracingMockStore) Close() error { return nil } From 3960f4240b13ef6641ce27d106038056cb8aefaf Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:42:23 +0100 Subject: [PATCH 08/26] add prune block to mockstore --- test/mocks/store.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/mocks/store.go b/test/mocks/store.go index 79b3ac6f55..44f81c1d66 100644 --- a/test/mocks/store.go +++ b/test/mocks/store.go @@ -39,6 +39,14 @@ func (_m *MockStore) EXPECT() *MockStore_Expecter { return &MockStore_Expecter{mock: &_m.Mock} } +// PruneBlocks provides a mock implementation for the Store's pruning method. +// Tests using MockStore currently do not exercise pruning behavior, so this +// method simply satisfies the interface and can be extended with expectations +// later if needed. +func (_mock *MockStore) PruneBlocks(ctx context.Context, height uint64) error { + return nil +} + // Close provides a mock function for the type MockStore func (_mock *MockStore) Close() error { ret := _mock.Called() From 0a63b7e2dbb3b751e4ad258244b28230d0f21ab5 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:49:29 +0100 Subject: [PATCH 09/26] fix helper for flag for consistency --- pkg/config/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 7a14aaae0a..f42bfb666b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -266,7 +266,7 @@ type NodeConfig struct { // When enabled, the node will periodically prune old block data (headers, data, // signatures, and hash index) from the local store while keeping recent history. PruningEnabled bool `mapstructure:"pruning_enabled" yaml:"pruning_enabled" comment:"Enable height-based pruning of stored block data. When disabled, all blocks are kept (archive mode)."` - PruningKeepRecent uint64 `mapstructure:"pruning_keep_recent" yaml:"pruning_keep_recent" comment:"Number of most recent blocks to retain. Older blocks will have their header/data/signature removed from the local store. 0 means keep all blocks."` + PruningKeepRecent uint64 `mapstructure:"pruning_keep_recent" yaml:"pruning_keep_recent" comment:"Number of most recent blocks to retain when pruning is enabled. Must be > 0 when pruning is enabled; set pruning_enabled=false to keep all blocks (archive mode)."` PruningInterval uint64 `mapstructure:"pruning_interval" yaml:"pruning_interval" comment:"Run pruning every N blocks. Must be >= 1 when pruning is enabled."` } @@ -460,7 +460,7 @@ func AddFlags(cmd *cobra.Command) { cmd.Flags().Duration(FlagScrapeInterval, def.Node.ScrapeInterval.Duration, "interval at which the reaper polls the execution layer for new transactions") // Pruning configuration flags cmd.Flags().Bool(FlagPrefixEvnode+"node.pruning_enabled", def.Node.PruningEnabled, "enable height-based pruning of stored block data (headers, data, signatures, index)") - cmd.Flags().Uint64(FlagPrefixEvnode+"node.pruning_keep_recent", def.Node.PruningKeepRecent, "number of most recent blocks to retain when pruning is enabled (0 = keep all)") + cmd.Flags().Uint64(FlagPrefixEvnode+"node.pruning_keep_recent", def.Node.PruningKeepRecent, "number of most recent blocks to retain when pruning is enabled (must be > 0; disable pruning to keep all blocks)") cmd.Flags().Uint64(FlagPrefixEvnode+"node.pruning_interval", def.Node.PruningInterval, "run pruning every N blocks (must be >= 1 when pruning is enabled)") // Data Availability configuration flags From 95f7f854824e7359c969c25c3d74361dbf4c2884 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:16:17 +0100 Subject: [PATCH 10/26] add replace statement for local packages --- apps/evm/go.mod | 1 + apps/evm/go.sum | 2 -- apps/grpc/go.mod | 1 + apps/grpc/go.sum | 2 -- apps/testapp/go.mod | 5 +++++ apps/testapp/go.sum | 4 ---- execution/evm/go.mod | 5 +++++ execution/evm/go.sum | 3 +++ go.mod | 3 +++ go.sum | 2 -- 10 files changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/evm/go.mod b/apps/evm/go.mod index 39bcd69887..19b8579932 100644 --- a/apps/evm/go.mod +++ b/apps/evm/go.mod @@ -4,6 +4,7 @@ go 1.25.6 replace ( github.com/evstack/ev-node => ../../ + github.com/evstack/ev-node/core => ../../core github.com/evstack/ev-node/execution/evm => ../../execution/evm ) diff --git a/apps/evm/go.sum b/apps/evm/go.sum index 5ea0339d44..58c363be55 100644 --- a/apps/evm/go.sum +++ b/apps/evm/go.sum @@ -411,8 +411,6 @@ github.com/ethereum/go-ethereum v1.16.8 h1:LLLfkZWijhR5m6yrAXbdlTeXoqontH+Ga2f9i github.com/ethereum/go-ethereum v1.16.8/go.mod h1:Fs6QebQbavneQTYcA39PEKv2+zIjX7rPUZ14DER46wk= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= -github.com/evstack/ev-node/core v1.0.0-rc.1 h1:Dic2PMUMAYUl5JW6DkDj6HXDEWYzorVJQuuUJOV0FjE= -github.com/evstack/ev-node/core v1.0.0-rc.1/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= diff --git a/apps/grpc/go.mod b/apps/grpc/go.mod index b547d3f5c7..73531392fe 100644 --- a/apps/grpc/go.mod +++ b/apps/grpc/go.mod @@ -4,6 +4,7 @@ go 1.25.6 replace ( github.com/evstack/ev-node => ../../ + github.com/evstack/ev-node/core => ../../core github.com/evstack/ev-node/execution/grpc => ../../execution/grpc ) diff --git a/apps/grpc/go.sum b/apps/grpc/go.sum index fb175ac5cd..d1fd875f09 100644 --- a/apps/grpc/go.sum +++ b/apps/grpc/go.sum @@ -367,8 +367,6 @@ github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6Ni github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= -github.com/evstack/ev-node/core v1.0.0-rc.1 h1:Dic2PMUMAYUl5JW6DkDj6HXDEWYzorVJQuuUJOV0FjE= -github.com/evstack/ev-node/core v1.0.0-rc.1/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= diff --git a/apps/testapp/go.mod b/apps/testapp/go.mod index cc0bf47212..4c57d790c1 100644 --- a/apps/testapp/go.mod +++ b/apps/testapp/go.mod @@ -2,6 +2,11 @@ module github.com/evstack/ev-node/apps/testapp go 1.25.6 +replace ( + github.com/evstack/ev-node => ../../. + github.com/evstack/ev-node/core => ../../core +) + require ( github.com/evstack/ev-node v1.0.0-rc.2 github.com/evstack/ev-node/core v1.0.0-rc.1 diff --git a/apps/testapp/go.sum b/apps/testapp/go.sum index 0645d79396..616ba29c45 100644 --- a/apps/testapp/go.sum +++ b/apps/testapp/go.sum @@ -367,10 +367,6 @@ github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6Ni github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= -github.com/evstack/ev-node v1.0.0-rc.2 h1:gUQzLTkCj6D751exm/FIR/yw2aXWiW2aEREEwtxMvw0= -github.com/evstack/ev-node v1.0.0-rc.2/go.mod h1:Qa2nN1D6PJQRU2tiarv6X5Der5OZg/+2QGY/K2mA760= -github.com/evstack/ev-node/core v1.0.0-rc.1 h1:Dic2PMUMAYUl5JW6DkDj6HXDEWYzorVJQuuUJOV0FjE= -github.com/evstack/ev-node/core v1.0.0-rc.1/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= diff --git a/execution/evm/go.mod b/execution/evm/go.mod index f4c560a7b5..c40351845c 100644 --- a/execution/evm/go.mod +++ b/execution/evm/go.mod @@ -103,3 +103,8 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.4.1 // indirect ) + +replace ( + github.com/evstack/ev-node => ../../ + github.com/evstack/ev-node/core => ../../core +) diff --git a/execution/evm/go.sum b/execution/evm/go.sum index 93b03d7e09..ba6335ae41 100644 --- a/execution/evm/go.sum +++ b/execution/evm/go.sum @@ -76,10 +76,13 @@ github.com/ethereum/go-ethereum v1.16.8 h1:LLLfkZWijhR5m6yrAXbdlTeXoqontH+Ga2f9i github.com/ethereum/go-ethereum v1.16.8/go.mod h1:Fs6QebQbavneQTYcA39PEKv2+zIjX7rPUZ14DER46wk= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= +<<<<<<< HEAD github.com/evstack/ev-node v1.0.0-rc.2 h1:gUQzLTkCj6D751exm/FIR/yw2aXWiW2aEREEwtxMvw0= github.com/evstack/ev-node v1.0.0-rc.2/go.mod h1:Qa2nN1D6PJQRU2tiarv6X5Der5OZg/+2QGY/K2mA760= github.com/evstack/ev-node/core v1.0.0-rc.1 h1:Dic2PMUMAYUl5JW6DkDj6HXDEWYzorVJQuuUJOV0FjE= github.com/evstack/ev-node/core v1.0.0-rc.1/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY= +======= +>>>>>>> faac1fd1 ( add replace statement for local packages) github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= diff --git a/go.mod b/go.mod index d07ea7a7e8..8f10b14e53 100644 --- a/go.mod +++ b/go.mod @@ -195,3 +195,6 @@ replace ( google.golang.org/genproto/googleapis/api => google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9 google.golang.org/genproto/googleapis/rpc => google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 ) + +// use local core module during development/CI +replace github.com/evstack/ev-node/core => ./core diff --git a/go.sum b/go.sum index fb175ac5cd..d1fd875f09 100644 --- a/go.sum +++ b/go.sum @@ -367,8 +367,6 @@ github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6Ni github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= -github.com/evstack/ev-node/core v1.0.0-rc.1 h1:Dic2PMUMAYUl5JW6DkDj6HXDEWYzorVJQuuUJOV0FjE= -github.com/evstack/ev-node/core v1.0.0-rc.1/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= From a88bc295f470f6d5bc4ae4729690d392a87005da Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:41:18 +0100 Subject: [PATCH 11/26] flags --- pkg/config/config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 1834e1b405..9189aa57e5 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -112,7 +112,7 @@ func TestAddFlags(t *testing.T) { assertFlagValue(t, flags, FlagRPCEnableDAVisualization, DefaultConfig().RPC.EnableDAVisualization) // Count the number of flags we're explicitly checking - expectedFlagCount := 63 // Update this number if you add more flag checks above + expectedFlagCount := 66 // Update this number if you add more flag checks above // Get the actual number of flags (both regular and persistent) actualFlagCount := 0 From 993ee4f9ad11e1a7f29aee931d541886b03b7fe8 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:03:17 +0100 Subject: [PATCH 12/26] add safetey mechanism for pruning only da included blocks --- block/internal/executing/executor.go | 49 +++++++++++++++++++--------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/block/internal/executing/executor.go b/block/internal/executing/executor.go index 426afdcf06..89b9c4f1f4 100644 --- a/block/internal/executing/executor.go +++ b/block/internal/executing/executor.go @@ -3,6 +3,7 @@ package executing import ( "bytes" "context" + "encoding/binary" "errors" "fmt" "reflect" @@ -551,23 +552,41 @@ func (e *Executor) ProduceBlock(ctx context.Context) error { // production to fail, but it does run in the critical path and may add // some latency when large ranges are pruned. if e.config.Node.PruningEnabled && e.config.Node.PruningKeepRecent > 0 && e.config.Node.PruningInterval > 0 { - if newHeight%e.config.Node.PruningInterval == 0 { - // Compute the prune floor: all heights <= targetHeight are candidates - // for pruning of header/data/signature/index entries. - if newHeight > e.config.Node.PruningKeepRecent { - targetHeight := newHeight - e.config.Node.PruningKeepRecent - if err := e.store.PruneBlocks(e.ctx, targetHeight); err != nil { - e.logger.Error().Err(err).Uint64("target_height", targetHeight).Msg("failed to prune old block data") + interval := e.config.Node.PruningInterval + // Only attempt pruning when we're exactly at an interval boundary. + if newHeight%interval == 0 && newHeight > e.config.Node.PruningKeepRecent { + targetHeight := newHeight - e.config.Node.PruningKeepRecent + + // Determine the DA-included floor for pruning, so we never prune + // beyond what has been confirmed in DA. + var daIncludedHeight uint64 + meta, err := e.store.GetMetadata(e.ctx, store.DAIncludedHeightKey) + if err == nil && len(meta) == 8 { + daIncludedHeight = binary.LittleEndian.Uint64(meta) + } + + // If nothing is known to be DA-included yet, skip pruning. + if daIncludedHeight == 0 { + // Nothing known to be DA-included yet; skip pruning. + } else { + if targetHeight > daIncludedHeight { + targetHeight = daIncludedHeight } - // If the execution client exposes execution-metadata pruning, - // prune ExecMeta using the same target height. This keeps EVM - // execution metadata aligned with ev-node's block store pruning - // while remaining a no-op for execution environments that don't - // implement ExecMetaPruner (e.g. ABCI-based executors). - if pruner, ok := e.exec.(coreexecutor.ExecMetaPruner); ok { - if err := pruner.PruneExecMeta(e.ctx, targetHeight); err != nil { - e.logger.Error().Err(err).Uint64("target_height", targetHeight).Msg("failed to prune execution metadata") + if targetHeight > 0 { + if err := e.store.PruneBlocks(e.ctx, targetHeight); err != nil { + return fmt.Errorf("failed to prune old block data: %w", err) + } + + // If the execution client exposes execution-metadata pruning, + // prune ExecMeta using the same target height. This keeps + // execution-layer metadata aligned with + // ev-node's block store pruning while remaining a no-op for + // execution environments that don't implement ExecMetaPruner yet. + if pruner, ok := e.exec.(coreexecutor.ExecMetaPruner); ok { + if err := pruner.PruneExecMeta(e.ctx, targetHeight); err != nil { + return fmt.Errorf("failed to prune execution metadata: %w", err) + } } } } From 186fd85ec7e059e9b76a02aa6131d8c00a5d90ea Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:12:43 +0100 Subject: [PATCH 13/26] fix rebase --- pkg/sync/sync_service.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/sync/sync_service.go b/pkg/sync/sync_service.go index c9e26fd31e..d4a5f0e818 100644 --- a/pkg/sync/sync_service.go +++ b/pkg/sync/sync_service.go @@ -477,10 +477,7 @@ func newSyncer[H header.Header[H]]( opts = append(opts, goheadersync.WithMetrics(), -<<<<<<< HEAD goheadersync.WithPruningWindow(ninetyNineYears), // pruning window not relevant, because of the store wrapper. -======= ->>>>>>> 3b9a0f70 (wiring prunning config to go-header) goheadersync.WithTrustingPeriod(ninetyNineYears), ) return goheadersync.NewSyncer(ex, store, sub, opts...) From 62a58d5660bf5cb1bad1b85c82797956bbf25356 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:39:34 +0100 Subject: [PATCH 14/26] remove useless check --- execution/evm/execution.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/execution/evm/execution.go b/execution/evm/execution.go index 795b8fa31e..c5ea2117cb 100644 --- a/execution/evm/execution.go +++ b/execution/evm/execution.go @@ -275,10 +275,6 @@ func NewEngineExecutionClient( // underlying EVMStore. It is safe to call this multiple times with the same // or increasing heights; the store tracks its own last-pruned height. func (c *EngineClient) PruneExecMeta(ctx context.Context, height uint64) error { - if c.store == nil { - return nil - } - return c.store.PruneExecMeta(ctx, height) } From e6edc198ed0b0dc9c1574054bdd7d74b04909e44 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:40:19 +0100 Subject: [PATCH 15/26] use lastprunedheight in Tail() to optimize --- pkg/store/store_adapter.go | 61 ++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/pkg/store/store_adapter.go b/pkg/store/store_adapter.go index ecd4da7abc..f74adeda4a 100644 --- a/pkg/store/store_adapter.go +++ b/pkg/store/store_adapter.go @@ -47,6 +47,12 @@ type EntityWithDAHint[H any] interface { DAHint() uint64 } +// lastPrunedHeightGetter is an optional interface that store getters can +// implement to expose the last pruned block height. +type lastPrunedHeightGetter interface { + LastPrunedHeight(ctx context.Context) (uint64, bool) +} + // heightSub provides a mechanism for waiting on a specific height to be stored. // This is critical for go-header syncer which expects GetByHeight to block until // the requested height is available. @@ -265,19 +271,30 @@ func (a *StoreAdapter[H]) Tail(ctx context.Context) (H, error) { height = h } - // Try genesisInitialHeight first (most common case - no pruning) - item, err := a.getter.GetByHeight(ctx, a.genesisInitialHeight) + // Determine the first candidate tail height. By default, this is the + // genesis initial height, but if pruning metadata is available we can + // skip directly past fully-pruned ranges. + startHeight := a.genesisInitialHeight + if getter, ok := any(a.getter).(lastPrunedHeightGetter); ok { + if lastPruned, ok := getter.LastPrunedHeight(ctx); ok { + if lastPruned < ^uint64(0) { + startHeight = lastPruned + 1 + } + } + } + + item, err := a.getter.GetByHeight(ctx, startHeight) if err == nil { return item, nil } - // Check pending for genesisInitialHeight - if pendingItem, ok := a.pending.Peek(a.genesisInitialHeight); ok { + // Check pending for the start height + if pendingItem, ok := a.pending.Peek(startHeight); ok { return pendingItem, nil } - // Walk up from genesisInitialHeight to find the first available item (pruning case) - for h := a.genesisInitialHeight + 1; h <= height; h++ { + // Walk up from startHeight to find the first available item + for h := startHeight + 1; h <= height; h++ { item, err = a.getter.GetByHeight(ctx, h) if err == nil { return item, nil @@ -641,6 +658,22 @@ func (g *HeaderStoreGetter) HasAt(ctx context.Context, height uint64) bool { return err == nil } +// LastPrunedHeight implements lastPrunedHeightGetter for HeaderStoreGetter by +// reading the pruning metadata from the underlying store. +func (g *HeaderStoreGetter) LastPrunedHeight(ctx context.Context) (uint64, bool) { + meta, err := g.store.GetMetadata(ctx, LastPrunedBlockHeightKey) + if err != nil || len(meta) != heightLength { + return 0, false + } + + height, err := decodeHeight(meta) + if err != nil { + return 0, false + } + + return height, true +} + // DataStoreGetter implements StoreGetter for *types.Data. type DataStoreGetter struct { store Store @@ -711,6 +744,22 @@ func (g *DataStoreGetter) HasAt(ctx context.Context, height uint64) bool { return err == nil } +// LastPrunedHeight implements lastPrunedHeightGetter for DataStoreGetter by +// reading the pruning metadata from the underlying store. +func (g *DataStoreGetter) LastPrunedHeight(ctx context.Context) (uint64, bool) { + meta, err := g.store.GetMetadata(ctx, LastPrunedBlockHeightKey) + if err != nil || len(meta) != heightLength { + return 0, false + } + + height, err := decodeHeight(meta) + if err != nil { + return 0, false + } + + return height, true +} + // Type aliases for convenience type HeaderStoreAdapter = StoreAdapter[*types.P2PSignedHeader] type DataStoreAdapter = StoreAdapter[*types.P2PData] From f2b9a47d0c146d84d28951d678660a505f1d8548 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:52:32 +0100 Subject: [PATCH 16/26] move pruning from executor to dainclusionloop --- block/internal/executing/executor.go | 47 -------------------------- block/internal/submitting/submitter.go | 33 ++++++++++++++++++ 2 files changed, 33 insertions(+), 47 deletions(-) diff --git a/block/internal/executing/executor.go b/block/internal/executing/executor.go index 89b9c4f1f4..bf1b44b6cb 100644 --- a/block/internal/executing/executor.go +++ b/block/internal/executing/executor.go @@ -3,7 +3,6 @@ package executing import ( "bytes" "context" - "encoding/binary" "errors" "fmt" "reflect" @@ -547,52 +546,6 @@ func (e *Executor) ProduceBlock(ctx context.Context) error { // Update in-memory state after successful commit e.setLastState(newState) - // Run height-based pruning of stored block data if enabled. This is a - // best-effort background maintenance step and should not cause block - // production to fail, but it does run in the critical path and may add - // some latency when large ranges are pruned. - if e.config.Node.PruningEnabled && e.config.Node.PruningKeepRecent > 0 && e.config.Node.PruningInterval > 0 { - interval := e.config.Node.PruningInterval - // Only attempt pruning when we're exactly at an interval boundary. - if newHeight%interval == 0 && newHeight > e.config.Node.PruningKeepRecent { - targetHeight := newHeight - e.config.Node.PruningKeepRecent - - // Determine the DA-included floor for pruning, so we never prune - // beyond what has been confirmed in DA. - var daIncludedHeight uint64 - meta, err := e.store.GetMetadata(e.ctx, store.DAIncludedHeightKey) - if err == nil && len(meta) == 8 { - daIncludedHeight = binary.LittleEndian.Uint64(meta) - } - - // If nothing is known to be DA-included yet, skip pruning. - if daIncludedHeight == 0 { - // Nothing known to be DA-included yet; skip pruning. - } else { - if targetHeight > daIncludedHeight { - targetHeight = daIncludedHeight - } - - if targetHeight > 0 { - if err := e.store.PruneBlocks(e.ctx, targetHeight); err != nil { - return fmt.Errorf("failed to prune old block data: %w", err) - } - - // If the execution client exposes execution-metadata pruning, - // prune ExecMeta using the same target height. This keeps - // execution-layer metadata aligned with - // ev-node's block store pruning while remaining a no-op for - // execution environments that don't implement ExecMetaPruner yet. - if pruner, ok := e.exec.(coreexecutor.ExecMetaPruner); ok { - if err := pruner.PruneExecMeta(e.ctx, targetHeight); err != nil { - return fmt.Errorf("failed to prune execution metadata: %w", err) - } - } - } - } - } - } - // broadcast header and data to P2P network g, broadcastCtx := errgroup.WithContext(e.ctx) g.Go(func() error { diff --git a/block/internal/submitting/submitter.go b/block/internal/submitting/submitter.go index 1c5b034c19..3b1cdd0d2d 100644 --- a/block/internal/submitting/submitter.go +++ b/block/internal/submitting/submitter.go @@ -360,6 +360,39 @@ func (s *Submitter) processDAInclusionLoop() { s.logger.Error().Err(err).Uint64("height", nextHeight).Msg("failed to persist DA included height") } + // Run height-based pruning if enabled. + if s.config.Node.PruningEnabled && s.config.Node.PruningKeepRecent > 0 && s.config.Node.PruningInterval > 0 { + // Trigger pruning only when we reach the configured interval. + if currentDAIncluded%s.config.Node.PruningInterval == 0 { + // We must make sure not to prune blocks that have not yet been included in DA. + daIncludedHeight := s.GetDAIncludedHeight() + + storeHeight, err := s.store.Height(s.ctx) + if err != nil { + s.logger.Error().Err(err).Msg("failed to get store height for pruning") + break + } + + upperBound := min(storeHeight, daIncludedHeight) + if upperBound <= s.config.Node.PruningKeepRecent { + // Not enough fully included blocks to prune while respecting keep-recent. + break + } + + targetHeight := upperBound - s.config.Node.PruningKeepRecent + + if err := s.store.PruneBlocks(s.ctx, targetHeight); err != nil { + s.logger.Error().Err(err).Uint64("target_height", targetHeight).Msg("failed to prune old block data") + } + + if pruner, ok := s.exec.(coreexecutor.ExecMetaPruner); ok { + if err := pruner.PruneExecMeta(s.ctx, targetHeight); err != nil { + s.logger.Error().Err(err).Uint64("target_height", targetHeight).Msg("failed to prune execution metadata") + } + } + } + } + // Delete height cache for that height // This can only be performed after the height has been persisted to store s.cache.DeleteHeight(nextHeight) From 6ce4a56fe89362938cadbebfa071c0fb4b246468 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:52:48 +0100 Subject: [PATCH 17/26] don't prune go-header store --- pkg/sync/sync_service.go | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pkg/sync/sync_service.go b/pkg/sync/sync_service.go index d4a5f0e818..8567e79764 100644 --- a/pkg/sync/sync_service.go +++ b/pkg/sync/sync_service.go @@ -189,21 +189,11 @@ func (syncService *SyncService[H]) Start(ctx context.Context) error { } // create syncer, must be before initFromP2PWithRetry which calls startSyncer. - syncOpts := []goheadersync.Option{goheadersync.WithBlockTime(syncService.conf.Node.BlockTime.Duration)} - // Map ev-node pruning configuration to go-header's pruning window: we approximate - // "keep N recent heights" as "retain headers for N * blockTime". - if syncService.conf.Node.PruningEnabled && syncService.conf.Node.PruningKeepRecent > 0 { - pruningWindow := syncService.conf.Node.BlockTime.Duration * time.Duration(syncService.conf.Node.PruningKeepRecent) - // Only set a pruning window if the computed duration is positive. - if pruningWindow > 0 { - syncOpts = append(syncOpts, goheadersync.WithPruningWindow(pruningWindow)) - } - } if syncService.syncer, err = newSyncer( syncService.ex, syncService.store, syncService.sub, - syncOpts, + []goheadersync.Option{goheadersync.WithBlockTime(syncService.conf.Node.BlockTime.Duration)}, ); err != nil { return fmt.Errorf("failed to create syncer: %w", err) } From 85ec99a498fcd3b03c073ded7c76725d2c3e9e2f Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:08:46 +0100 Subject: [PATCH 18/26] update tail function --- pkg/store/store_adapter.go | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/pkg/store/store_adapter.go b/pkg/store/store_adapter.go index f74adeda4a..5a1d77fc57 100644 --- a/pkg/store/store_adapter.go +++ b/pkg/store/store_adapter.go @@ -275,7 +275,7 @@ func (a *StoreAdapter[H]) Tail(ctx context.Context) (H, error) { // genesis initial height, but if pruning metadata is available we can // skip directly past fully-pruned ranges. startHeight := a.genesisInitialHeight - if getter, ok := any(a.getter).(lastPrunedHeightGetter); ok { + if getter, ok := a.getter.(lastPrunedHeightGetter); ok { if lastPruned, ok := getter.LastPrunedHeight(ctx); ok { if lastPruned < ^uint64(0) { startHeight = lastPruned + 1 @@ -658,22 +658,6 @@ func (g *HeaderStoreGetter) HasAt(ctx context.Context, height uint64) bool { return err == nil } -// LastPrunedHeight implements lastPrunedHeightGetter for HeaderStoreGetter by -// reading the pruning metadata from the underlying store. -func (g *HeaderStoreGetter) LastPrunedHeight(ctx context.Context) (uint64, bool) { - meta, err := g.store.GetMetadata(ctx, LastPrunedBlockHeightKey) - if err != nil || len(meta) != heightLength { - return 0, false - } - - height, err := decodeHeight(meta) - if err != nil { - return 0, false - } - - return height, true -} - // DataStoreGetter implements StoreGetter for *types.Data. type DataStoreGetter struct { store Store From 9458b03738812af247fbba9678fa6310c677e3d8 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:13:18 +0100 Subject: [PATCH 19/26] rename execmetapruner to execpruner --- block/internal/submitting/submitter.go | 4 ++-- core/execution/execution.go | 8 ++++---- execution/evm/execution.go | 8 ++++---- execution/evm/go.sum | 7 ------- execution/evm/store.go | 4 ++-- execution/evm/store_test.go | 12 ++++++------ 6 files changed, 18 insertions(+), 25 deletions(-) diff --git a/block/internal/submitting/submitter.go b/block/internal/submitting/submitter.go index 3b1cdd0d2d..28f6252435 100644 --- a/block/internal/submitting/submitter.go +++ b/block/internal/submitting/submitter.go @@ -385,8 +385,8 @@ func (s *Submitter) processDAInclusionLoop() { s.logger.Error().Err(err).Uint64("target_height", targetHeight).Msg("failed to prune old block data") } - if pruner, ok := s.exec.(coreexecutor.ExecMetaPruner); ok { - if err := pruner.PruneExecMeta(s.ctx, targetHeight); err != nil { + if pruner, ok := s.exec.(coreexecutor.ExecPruner); ok { + if err := pruner.PruneExec(s.ctx, targetHeight); err != nil { s.logger.Error().Err(err).Uint64("target_height", targetHeight).Msg("failed to prune execution metadata") } } diff --git a/core/execution/execution.go b/core/execution/execution.go index 1f85e2068a..4eb8482f2c 100644 --- a/core/execution/execution.go +++ b/core/execution/execution.go @@ -162,15 +162,15 @@ type Rollbackable interface { Rollback(ctx context.Context, targetHeight uint64) error } -// ExecMetaPruner is an optional interface that execution clients can implement +// ExecPruner is an optional interface that execution clients can implement // to support height-based pruning of their execution metadata. This is used by // EVM-based execution clients to keep ExecMeta consistent with ev-node's // pruning window while remaining a no-op for execution environments that // don't persist per-height metadata in ev-node's datastore. -type ExecMetaPruner interface { - // PruneExecMeta should delete execution metadata for all heights up to and +type ExecPruner interface { + // PruneExec should delete execution metadata for all heights up to and // including the given height. Implementations should be idempotent and track // their own progress so that repeated calls with the same or decreasing // heights are cheap no-ops. - PruneExecMeta(ctx context.Context, height uint64) error + PruneExec(ctx context.Context, height uint64) error } diff --git a/execution/evm/execution.go b/execution/evm/execution.go index c5ea2117cb..3c56c24574 100644 --- a/execution/evm/execution.go +++ b/execution/evm/execution.go @@ -69,7 +69,7 @@ var _ execution.Rollbackable = (*EngineClient)(nil) // ev-node's height-based pruning. This enables coordinated pruning of EVM // ExecMeta alongside ev-node's own block data pruning, while remaining a // no-op for non-EVM execution environments. -var _ execution.ExecMetaPruner = (*EngineClient)(nil) +var _ execution.ExecPruner = (*EngineClient)(nil) // validatePayloadStatus checks the payload status and returns appropriate errors. // It implements the Engine API specification's status handling: @@ -271,11 +271,11 @@ func NewEngineExecutionClient( }, nil } -// PruneExecMeta implements execution.ExecMetaPruner by delegating to the +// PruneExec implements execution.ExecPruner by delegating to the // underlying EVMStore. It is safe to call this multiple times with the same // or increasing heights; the store tracks its own last-pruned height. -func (c *EngineClient) PruneExecMeta(ctx context.Context, height uint64) error { - return c.store.PruneExecMeta(ctx, height) +func (c *EngineClient) PruneExec(ctx context.Context, height uint64) error { + return c.store.PruneExec(ctx, height) } // SetLogger allows callers to attach a structured logger. diff --git a/execution/evm/go.sum b/execution/evm/go.sum index ba6335ae41..91fd1f2764 100644 --- a/execution/evm/go.sum +++ b/execution/evm/go.sum @@ -76,13 +76,6 @@ github.com/ethereum/go-ethereum v1.16.8 h1:LLLfkZWijhR5m6yrAXbdlTeXoqontH+Ga2f9i github.com/ethereum/go-ethereum v1.16.8/go.mod h1:Fs6QebQbavneQTYcA39PEKv2+zIjX7rPUZ14DER46wk= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= -<<<<<<< HEAD -github.com/evstack/ev-node v1.0.0-rc.2 h1:gUQzLTkCj6D751exm/FIR/yw2aXWiW2aEREEwtxMvw0= -github.com/evstack/ev-node v1.0.0-rc.2/go.mod h1:Qa2nN1D6PJQRU2tiarv6X5Der5OZg/+2QGY/K2mA760= -github.com/evstack/ev-node/core v1.0.0-rc.1 h1:Dic2PMUMAYUl5JW6DkDj6HXDEWYzorVJQuuUJOV0FjE= -github.com/evstack/ev-node/core v1.0.0-rc.1/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY= -======= ->>>>>>> faac1fd1 ( add replace statement for local packages) github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= diff --git a/execution/evm/store.go b/execution/evm/store.go index 51a3056b66..21fe5008ae 100644 --- a/execution/evm/store.go +++ b/execution/evm/store.go @@ -145,10 +145,10 @@ func (s *EVMStore) SaveExecMeta(ctx context.Context, meta *ExecMeta) error { return nil } -// PruneExecMeta removes ExecMeta entries up to and including the given height. +// PruneExec removes ExecMeta entries up to and including the given height. // It is safe to call this multiple times with the same or increasing heights; // previously pruned ranges will be skipped based on the last-pruned marker. -func (s *EVMStore) PruneExecMeta(ctx context.Context, height uint64) error { +func (s *EVMStore) PruneExec(ctx context.Context, height uint64) error { // Load last pruned height, if any. var lastPruned uint64 data, err := s.db.Get(ctx, ds.NewKey(lastPrunedExecMetaKey)) diff --git a/execution/evm/store_test.go b/execution/evm/store_test.go index 64389701f9..d3067fc1a6 100644 --- a/execution/evm/store_test.go +++ b/execution/evm/store_test.go @@ -17,7 +17,7 @@ func newTestDatastore(t *testing.T) ds.Batching { return dssync.MutexWrap(ds.NewMapDatastore()) } -func TestPruneExecMeta_PrunesUpToTargetHeight(t *testing.T) { +func TestPruneExec_PrunesUpToTargetHeight(t *testing.T) { t.Parallel() ctx := context.Background() @@ -39,7 +39,7 @@ func TestPruneExecMeta_PrunesUpToTargetHeight(t *testing.T) { } // Prune up to height 3 - require.NoError(t, store.PruneExecMeta(ctx, 3)) + require.NoError(t, store.PruneExec(ctx, 3)) // Heights 1..3 should be gone for h := uint64(1); h <= 3; h++ { @@ -56,10 +56,10 @@ func TestPruneExecMeta_PrunesUpToTargetHeight(t *testing.T) { } // Re-pruning with the same height should be a no-op - require.NoError(t, store.PruneExecMeta(ctx, 3)) + require.NoError(t, store.PruneExec(ctx, 3)) } -func TestPruneExecMeta_TracksLastPrunedHeight(t *testing.T) { +func TestPruneExec_TracksLastPrunedHeight(t *testing.T) { t.Parallel() ctx := context.Background() @@ -73,10 +73,10 @@ func TestPruneExecMeta_TracksLastPrunedHeight(t *testing.T) { } // First prune up to 2 - require.NoError(t, store.PruneExecMeta(ctx, 2)) + require.NoError(t, store.PruneExec(ctx, 2)) // Then prune up to 4; heights 3..4 should be deleted in this run - require.NoError(t, store.PruneExecMeta(ctx, 4)) + require.NoError(t, store.PruneExec(ctx, 4)) // Verify all heights 1..4 are gone, 5 remains for h := uint64(1); h <= 4; h++ { From ddc064a71d4c279dd47f12f618ad5c0426d858da Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:30:46 +0100 Subject: [PATCH 20/26] fix go mod and go sum --- apps/testapp/go.sum | 3 ++- execution/evm/go.mod | 2 +- execution/evm/go.sum | 50 +++++++++++++++++++++++--------------------- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/apps/testapp/go.sum b/apps/testapp/go.sum index 616ba29c45..d1fd875f09 100644 --- a/apps/testapp/go.sum +++ b/apps/testapp/go.sum @@ -582,8 +582,9 @@ github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP github.com/hashicorp/go-msgpack/v2 v2.1.2 h1:4Ee8FTp834e+ewB71RDrQ0VKpyFdrKOjvYtnQ/ltVj0= github.com/hashicorp/go-msgpack/v2 v2.1.2/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= diff --git a/execution/evm/go.mod b/execution/evm/go.mod index c40351845c..c0e6399899 100644 --- a/execution/evm/go.mod +++ b/execution/evm/go.mod @@ -91,7 +91,7 @@ require ( go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.47.0 // indirect - golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect + golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect golang.org/x/net v0.49.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.40.0 // indirect diff --git a/execution/evm/go.sum b/execution/evm/go.sum index 91fd1f2764..3294e40da9 100644 --- a/execution/evm/go.sum +++ b/execution/evm/go.sum @@ -64,6 +64,8 @@ github.com/dgraph-io/badger/v4 v4.5.1 h1:7DCIXrQjo1LKmM96YD+hLVJ2EEsyyoWxJfpdd56 github.com/dgraph-io/badger/v4 v4.5.1/go.mod h1:qn3Be0j3TfV4kPbVoK0arXCD1/nr1ftth6sbL5jxdoA= github.com/dgraph-io/ristretto/v2 v2.1.0 h1:59LjpOJLNDULHh8MC4UaegN52lC4JnO2dITsie/Pa8I= github.com/dgraph-io/ristretto/v2 v2.1.0/go.mod h1:uejeqfYXpUomfse0+lO+13ATz4TypQYLJZzBSAemuB4= +github.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54= +github.com/dunglas/httpsfv v1.1.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= @@ -131,8 +133,8 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/flatbuffers v24.12.23+incompatible h1:ubBKR94NR4pXUCY/MUsRVzd9umNW7ht7EG9hHfS9FX8= -github.com/google/flatbuffers v24.12.23+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v25.1.24+incompatible h1:4wPqL3K7GzBd1CwyhSd3usxLKOaJN/AC6puCca6Jm7o= +github.com/google/flatbuffers v25.1.24+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -176,8 +178,8 @@ github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ds-badger4 v0.1.8 h1:frNczf5CjCVm62RJ5mW5tD/oLQY/9IKAUpKviRV9QAI= github.com/ipfs/go-ds-badger4 v0.1.8/go.mod h1:FdqSLA5TMsyqooENB/Hf4xzYE/iH0z/ErLD6ogtfMrA= -github.com/ipfs/go-log/v2 v2.9.0 h1:l4b06AwVXwldIzbVPZy5z7sKp9lHFTX0KWfTBCtHaOk= -github.com/ipfs/go-log/v2 v2.9.0/go.mod h1:UhIYAwMV7Nb4ZmihUxfIRM2Istw/y9cAk3xaK+4Zs2c= +github.com/ipfs/go-log/v2 v2.9.1 h1:3JXwHWU31dsCpvQ+7asz6/QsFJHqFr4gLgQ0FWteujk= +github.com/ipfs/go-log/v2 v2.9.1/go.mod h1:evFx7sBiohUN3AG12mXlZBw5hacBQld3ZPHrowlJYoo= github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E= github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= @@ -204,12 +206,12 @@ github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38y github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-flow-metrics v0.3.0 h1:q31zcHUvHnwDO0SHaukewPYgwOBSxtt830uJtUx6784= github.com/libp2p/go-flow-metrics v0.3.0/go.mod h1:nuhlreIwEguM1IvHAew3ij7A8BMlyHQJ279ao24eZZo= -github.com/libp2p/go-libp2p v0.46.0 h1:0T2yvIKpZ3DVYCuPOFxPD1layhRU486pj9rSlGWYnDM= -github.com/libp2p/go-libp2p v0.46.0/go.mod h1:TbIDnpDjBLa7isdgYpbxozIVPBTmM/7qKOJP4SFySrQ= +github.com/libp2p/go-libp2p v0.47.0 h1:qQpBjSCWNQFF0hjBbKirMXE9RHLtSuzTDkTfr1rw0yc= +github.com/libp2p/go-libp2p v0.47.0/go.mod h1:s8HPh7mMV933OtXzONaGFseCg/BE//m1V34p3x4EUOY= github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= -github.com/libp2p/go-libp2p-kad-dht v0.37.0 h1:V1IkFzK9taNS1UNAx260foulcBPH+watAUFjNo2qMUY= -github.com/libp2p/go-libp2p-kad-dht v0.37.0/go.mod h1:o4FPa1ea++UVAMJ1c+kyjUmj3CKm9+ZCyzQb4uutCFM= +github.com/libp2p/go-libp2p-kad-dht v0.37.1 h1:jtX8bQIXVCs6/allskNB4m5n95Xvwav7wHAhopGZfS0= +github.com/libp2p/go-libp2p-kad-dht v0.37.1/go.mod h1:Uwokdh232k9Y1uMy2yJOK5zb7hpMHn4P8uWS4s9i05Q= github.com/libp2p/go-libp2p-kbucket v0.8.0 h1:QAK7RzKJpYe+EuSEATAaaHYMYLkPDGC18m9jxPLnU8s= github.com/libp2p/go-libp2p-kbucket v0.8.0/go.mod h1:JMlxqcEyKwO6ox716eyC0hmiduSWZZl6JY93mGaaqc4= github.com/libp2p/go-libp2p-pubsub v0.15.0 h1:cG7Cng2BT82WttmPFMi50gDNV+58K626m/wR00vGL1o= @@ -220,8 +222,8 @@ github.com/libp2p/go-libp2p-routing-helpers v0.7.5 h1:HdwZj9NKovMx0vqq6YNPTh6aaN github.com/libp2p/go-libp2p-routing-helpers v0.7.5/go.mod h1:3YaxrwP0OBPDD7my3D0KxfR89FlcX/IEbxDEDfAmj98= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= -github.com/libp2p/go-netroute v0.3.0 h1:nqPCXHmeNmgTJnktosJ/sIef9hvwYCrsLxXmfNks/oc= -github.com/libp2p/go-netroute v0.3.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA= +github.com/libp2p/go-netroute v0.4.0 h1:sZZx9hyANYUx9PZyqcgE/E1GUG3iEtTZHUEvdtXT7/Q= +github.com/libp2p/go-netroute v0.4.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/libp2p/go-yamux/v5 v5.0.1 h1:f0WoX/bEF2E8SbE4c/k1Mo+/9z0O4oC/hWEA+nfYRSg= @@ -350,10 +352,10 @@ github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkq github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10= -github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= -github.com/quic-go/webtransport-go v0.9.0 h1:jgys+7/wm6JarGDrW+lD/r9BGqBAmqY/ssklE09bA70= -github.com/quic-go/webtransport-go v0.9.0/go.mod h1:4FUYIiUc75XSsF6HShcLeXXYZJ9AGwo/xh3L8M/P1ao= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/quic-go/webtransport-go v0.10.0 h1:LqXXPOXuETY5Xe8ITdGisBzTYmUOy5eSj+9n4hLTjHI= +github.com/quic-go/webtransport-go v0.10.0/go.mod h1:LeGIXr5BQKE3UsynwVBeQrU1TPrbh73MGoC6jd+V7ow= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -451,11 +453,11 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= -golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -496,8 +498,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc h1:bH6xUXay0AIFMElXG2rQ4uiE+7ncwtiOdPfYK1NK2XA= -golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 h1:O1cMQHRfwNpDfDJerqRoE2oD+AFlyid87D40L/OkkJo= +golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -511,15 +513,15 @@ golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= From 3472938f4a8d1490e6ef3dbe4f3334b5a4a3fa21 Mon Sep 17 00:00:00 2001 From: Pierrick <9058370+pthmas@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:40:24 +0100 Subject: [PATCH 21/26] Update core/execution/execution.go Co-authored-by: julienrbrt --- core/execution/execution.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/execution/execution.go b/core/execution/execution.go index 4eb8482f2c..f3ebe1da91 100644 --- a/core/execution/execution.go +++ b/core/execution/execution.go @@ -163,10 +163,7 @@ type Rollbackable interface { } // ExecPruner is an optional interface that execution clients can implement -// to support height-based pruning of their execution metadata. This is used by -// EVM-based execution clients to keep ExecMeta consistent with ev-node's -// pruning window while remaining a no-op for execution environments that -// don't persist per-height metadata in ev-node's datastore. +// to support height-based pruning of their execution metadata. type ExecPruner interface { // PruneExec should delete execution metadata for all heights up to and // including the given height. Implementations should be idempotent and track From 75c241b91eedcd55d011d464edd22945edeb0cb7 Mon Sep 17 00:00:00 2001 From: Pierrick <9058370+pthmas@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:41:30 +0100 Subject: [PATCH 22/26] Update execution/evm/execution.go Co-authored-by: julienrbrt --- execution/evm/execution.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/execution/evm/execution.go b/execution/evm/execution.go index 3c56c24574..014ff16389 100644 --- a/execution/evm/execution.go +++ b/execution/evm/execution.go @@ -66,9 +66,7 @@ var _ execution.HeightProvider = (*EngineClient)(nil) var _ execution.Rollbackable = (*EngineClient)(nil) // Ensure EngineClient implements optional pruning interface when used with -// ev-node's height-based pruning. This enables coordinated pruning of EVM -// ExecMeta alongside ev-node's own block data pruning, while remaining a -// no-op for non-EVM execution environments. +// ev-node's height-based pruning. var _ execution.ExecPruner = (*EngineClient)(nil) // validatePayloadStatus checks the payload status and returns appropriate errors. From 8ddde04346617de349da755d68c4821c2369f234 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:29:40 +0100 Subject: [PATCH 23/26] trigger pruning every ticker --- block/internal/submitting/submitter.go | 65 ++++++++++++++------------ 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/block/internal/submitting/submitter.go b/block/internal/submitting/submitter.go index 28f6252435..886f1ac0f9 100644 --- a/block/internal/submitting/submitter.go +++ b/block/internal/submitting/submitter.go @@ -360,42 +360,47 @@ func (s *Submitter) processDAInclusionLoop() { s.logger.Error().Err(err).Uint64("height", nextHeight).Msg("failed to persist DA included height") } - // Run height-based pruning if enabled. - if s.config.Node.PruningEnabled && s.config.Node.PruningKeepRecent > 0 && s.config.Node.PruningInterval > 0 { - // Trigger pruning only when we reach the configured interval. - if currentDAIncluded%s.config.Node.PruningInterval == 0 { - // We must make sure not to prune blocks that have not yet been included in DA. - daIncludedHeight := s.GetDAIncludedHeight() - - storeHeight, err := s.store.Height(s.ctx) - if err != nil { - s.logger.Error().Err(err).Msg("failed to get store height for pruning") - break - } + // Delete height cache for that height + // This can only be performed after the height has been persisted to store + s.cache.DeleteHeight(nextHeight) + } - upperBound := min(storeHeight, daIncludedHeight) - if upperBound <= s.config.Node.PruningKeepRecent { - // Not enough fully included blocks to prune while respecting keep-recent. - break - } + // Run height-based pruning if enabled. + if s.config.Node.PruningEnabled && s.config.Node.PruningKeepRecent > 0 && s.config.Node.PruningInterval > 0 { + currentDAIncluded = s.GetDAIncludedHeight() - targetHeight := upperBound - s.config.Node.PruningKeepRecent + var lastPruned uint64 + if bz, err := s.store.GetMetadata(s.ctx, store.LastPrunedBlockHeightKey); err == nil && len(bz) == 8 { + lastPruned = binary.LittleEndian.Uint64(bz) + } - if err := s.store.PruneBlocks(s.ctx, targetHeight); err != nil { - s.logger.Error().Err(err).Uint64("target_height", targetHeight).Msg("failed to prune old block data") - } + storeHeight, err := s.store.Height(s.ctx) + if err != nil { + s.logger.Error().Err(err).Msg("failed to get store height for pruning") + continue + } + if storeHeight <= lastPruned+uint64(s.config.Node.PruningInterval) { + continue + } - if pruner, ok := s.exec.(coreexecutor.ExecPruner); ok { - if err := pruner.PruneExec(s.ctx, targetHeight); err != nil { - s.logger.Error().Err(err).Uint64("target_height", targetHeight).Msg("failed to prune execution metadata") - } - } - } + // Never prune blocks that are not DA included + upperBound := min(storeHeight, currentDAIncluded) + if upperBound <= s.config.Node.PruningKeepRecent { + // Not enough fully included blocks to prune + continue } - // Delete height cache for that height - // This can only be performed after the height has been persisted to store - s.cache.DeleteHeight(nextHeight) + targetHeight := upperBound - s.config.Node.PruningKeepRecent + + if err := s.store.PruneBlocks(s.ctx, targetHeight); err != nil { + s.logger.Error().Err(err).Uint64("target_height", targetHeight).Msg("failed to prune old block data") + } + + if pruner, ok := s.exec.(coreexecutor.ExecPruner); ok { + if err := pruner.PruneExec(s.ctx, targetHeight); err != nil { + s.logger.Error().Err(err).Uint64("target_height", targetHeight).Msg("failed to prune execution metadata") + } + } } } } From 20d578b1f2d869b568ae209068c00726ff305116 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:25:10 +0100 Subject: [PATCH 24/26] add store to store adapter --- block/internal/submitting/submitter.go | 2 +- pkg/store/store_adapter.go | 41 ++++++++------------------ 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/block/internal/submitting/submitter.go b/block/internal/submitting/submitter.go index 886f1ac0f9..6b1a2971f9 100644 --- a/block/internal/submitting/submitter.go +++ b/block/internal/submitting/submitter.go @@ -379,7 +379,7 @@ func (s *Submitter) processDAInclusionLoop() { s.logger.Error().Err(err).Msg("failed to get store height for pruning") continue } - if storeHeight <= lastPruned+uint64(s.config.Node.PruningInterval) { + if storeHeight <= lastPruned+s.config.Node.PruningInterval { continue } diff --git a/pkg/store/store_adapter.go b/pkg/store/store_adapter.go index 5a1d77fc57..1fd80af4b7 100644 --- a/pkg/store/store_adapter.go +++ b/pkg/store/store_adapter.go @@ -47,12 +47,6 @@ type EntityWithDAHint[H any] interface { DAHint() uint64 } -// lastPrunedHeightGetter is an optional interface that store getters can -// implement to expose the last pruned block height. -type lastPrunedHeightGetter interface { - LastPrunedHeight(ctx context.Context) (uint64, bool) -} - // heightSub provides a mechanism for waiting on a specific height to be stored. // This is critical for go-header syncer which expects GetByHeight to block until // the requested height is available. @@ -134,6 +128,7 @@ func (hs *heightSub) notifyUpTo(h uint64) { // a block, it writes to the underlying store, and subsequent reads will come from the store. type StoreAdapter[H EntityWithDAHint[H]] struct { getter StoreGetter[H] + store Store genesisInitialHeight uint64 // heightSub tracks the current height and allows waiting for specific heights. @@ -275,10 +270,12 @@ func (a *StoreAdapter[H]) Tail(ctx context.Context) (H, error) { // genesis initial height, but if pruning metadata is available we can // skip directly past fully-pruned ranges. startHeight := a.genesisInitialHeight - if getter, ok := a.getter.(lastPrunedHeightGetter); ok { - if lastPruned, ok := getter.LastPrunedHeight(ctx); ok { - if lastPruned < ^uint64(0) { - startHeight = lastPruned + 1 + if a.store != nil { + if meta, err := a.store.GetMetadata(ctx, LastPrunedBlockHeightKey); err == nil && len(meta) == heightLength { + if lastPruned, err := decodeHeight(meta); err == nil { + if candidate := lastPruned + 1; candidate > startHeight { + startHeight = candidate + } } } } @@ -728,22 +725,6 @@ func (g *DataStoreGetter) HasAt(ctx context.Context, height uint64) bool { return err == nil } -// LastPrunedHeight implements lastPrunedHeightGetter for DataStoreGetter by -// reading the pruning metadata from the underlying store. -func (g *DataStoreGetter) LastPrunedHeight(ctx context.Context) (uint64, bool) { - meta, err := g.store.GetMetadata(ctx, LastPrunedBlockHeightKey) - if err != nil || len(meta) != heightLength { - return 0, false - } - - height, err := decodeHeight(meta) - if err != nil { - return 0, false - } - - return height, true -} - // Type aliases for convenience type HeaderStoreAdapter = StoreAdapter[*types.P2PSignedHeader] type DataStoreAdapter = StoreAdapter[*types.P2PData] @@ -751,11 +732,15 @@ type DataStoreAdapter = StoreAdapter[*types.P2PData] // NewHeaderStoreAdapter creates a new StoreAdapter for headers. // The genesis is used to determine the initial height for efficient Tail lookups. func NewHeaderStoreAdapter(store Store, gen genesis.Genesis) *HeaderStoreAdapter { - return NewStoreAdapter(NewHeaderStoreGetter(store), gen) + adapter := NewStoreAdapter(NewHeaderStoreGetter(store), gen) + adapter.store = store + return adapter } // NewDataStoreAdapter creates a new StoreAdapter for data. // The genesis is used to determine the initial height for efficient Tail lookups. func NewDataStoreAdapter(store Store, gen genesis.Genesis) *DataStoreAdapter { - return NewStoreAdapter(NewDataStoreGetter(store), gen) + adapter := NewStoreAdapter(NewDataStoreGetter(store), gen) + adapter.store = store + return adapter } From 34a78908c0aba1309f4a1aac6d78839fc0385e46 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:57:28 +0100 Subject: [PATCH 25/26] invalidate cache store data after pruning --- pkg/store/cached_store.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/store/cached_store.go b/pkg/store/cached_store.go index e59d4e28c2..3fdabc9e5f 100644 --- a/pkg/store/cached_store.go +++ b/pkg/store/cached_store.go @@ -157,6 +157,19 @@ func (cs *CachedStore) Rollback(ctx context.Context, height uint64, aggregator b return nil } +// PruneBlocks wraps the underlying store's PruneBlocks and invalidates caches. +// After pruning historical block data from disk, any cached entries for pruned +// heights must not be served, so we conservatively clear the entire cache. +func (cs *CachedStore) PruneBlocks(ctx context.Context, height uint64) error { + if err := cs.Store.PruneBlocks(ctx, height); err != nil { + return err + } + + // Invalidate cache for pruned heights + cs.InvalidateRange(1, height) + return nil +} + // Close closes the underlying store. func (cs *CachedStore) Close() error { cs.ClearCache() From 86d8a2dcf1de2a2d73ef3fb432ae15194bdac5ec Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Wed, 4 Feb 2026 20:21:19 +0100 Subject: [PATCH 26/26] nit --- pkg/store/store_adapter.go | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/pkg/store/store_adapter.go b/pkg/store/store_adapter.go index 1fd80af4b7..6731cfe476 100644 --- a/pkg/store/store_adapter.go +++ b/pkg/store/store_adapter.go @@ -151,9 +151,9 @@ type StoreAdapter[H EntityWithDAHint[H]] struct { onDeleteFn func(context.Context, uint64) error } -// NewStoreAdapter creates a new StoreAdapter wrapping the given store getter. +// NewStoreAdapter creates a new StoreAdapter wrapping the given store getter and backing Store. // The genesis is used to determine the initial height for efficient Tail lookups. -func NewStoreAdapter[H EntityWithDAHint[H]](getter StoreGetter[H], gen genesis.Genesis) *StoreAdapter[H] { +func NewStoreAdapter[H EntityWithDAHint[H]](getter StoreGetter[H], store Store, gen genesis.Genesis) *StoreAdapter[H] { // Create LRU cache for pending items - ignore error as size is constant and valid pendingCache, _ := lru.New[uint64, H](defaultPendingCacheSize) daHintsCache, _ := lru.New[uint64, uint64](defaultPendingCacheSize) @@ -166,6 +166,7 @@ func NewStoreAdapter[H EntityWithDAHint[H]](getter StoreGetter[H], gen genesis.G adapter := &StoreAdapter[H]{ getter: getter, + store: store, genesisInitialHeight: max(gen.InitialHeight, 1), pending: pendingCache, daHints: daHintsCache, @@ -252,7 +253,6 @@ func (a *StoreAdapter[H]) Head(ctx context.Context, _ ...header.HeadOption[H]) ( // Tail returns the lowest item in the store. // For ev-node, this is typically the genesis/initial height. // If pruning has occurred, it walks up from initialHeight to find the first available item. -// TODO(@julienrbrt): Optimize this when pruning is enabled. func (a *StoreAdapter[H]) Tail(ctx context.Context) (H, error) { var zero H @@ -270,12 +270,10 @@ func (a *StoreAdapter[H]) Tail(ctx context.Context) (H, error) { // genesis initial height, but if pruning metadata is available we can // skip directly past fully-pruned ranges. startHeight := a.genesisInitialHeight - if a.store != nil { - if meta, err := a.store.GetMetadata(ctx, LastPrunedBlockHeightKey); err == nil && len(meta) == heightLength { - if lastPruned, err := decodeHeight(meta); err == nil { - if candidate := lastPruned + 1; candidate > startHeight { - startHeight = candidate - } + if meta, err := a.store.GetMetadata(ctx, LastPrunedBlockHeightKey); err == nil && len(meta) == heightLength { + if lastPruned, err := decodeHeight(meta); err == nil { + if candidate := lastPruned + 1; candidate > startHeight { + startHeight = candidate } } } @@ -732,15 +730,11 @@ type DataStoreAdapter = StoreAdapter[*types.P2PData] // NewHeaderStoreAdapter creates a new StoreAdapter for headers. // The genesis is used to determine the initial height for efficient Tail lookups. func NewHeaderStoreAdapter(store Store, gen genesis.Genesis) *HeaderStoreAdapter { - adapter := NewStoreAdapter(NewHeaderStoreGetter(store), gen) - adapter.store = store - return adapter + return NewStoreAdapter(NewHeaderStoreGetter(store), store, gen) } // NewDataStoreAdapter creates a new StoreAdapter for data. // The genesis is used to determine the initial height for efficient Tail lookups. func NewDataStoreAdapter(store Store, gen genesis.Genesis) *DataStoreAdapter { - adapter := NewStoreAdapter(NewDataStoreGetter(store), gen) - adapter.store = store - return adapter + return NewStoreAdapter(NewDataStoreGetter(store), store, gen) }