From 9335fb154c6383f9416279ab6145f0015c260952 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:52:34 +0100 Subject: [PATCH 1/2] prunning --- pkg/adapter/adapter.go | 13 ++++++++++ pkg/store/store.go | 56 +++++++++++++++++++++++++++++++++++++++++ pkg/store/store_test.go | 53 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/pkg/adapter/adapter.go b/pkg/adapter/adapter.go index 8127774a..25431c6c 100644 --- a/pkg/adapter/adapter.go +++ b/pkg/adapter/adapter.go @@ -90,6 +90,19 @@ type Adapter struct { stackedEvents []StackedEvent } +// PruneExecMeta implements execution.ExecMetaPruner for the ABCI adapter by +// delegating to the underlying ev-abci exec store. It prunes per-height ABCI +// execution metadata (block IDs and block responses) up to the given height. +// The method is safe to call multiple times with the same or increasing +// heights. +func (a *Adapter) PruneExecMeta(ctx context.Context, height uint64) error { + if a.Store == nil { + return nil + } + + return a.Store.Prune(ctx, height) +} + // NewABCIExecutor creates a new Adapter instance that implements the go-execution.Executor interface. // The Adapter wraps the provided ABCI application and delegates execution-related operations to it. func NewABCIExecutor( diff --git a/pkg/store/store.go b/pkg/store/store.go index ebd05dcc..381ee33d 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -2,6 +2,7 @@ package store import ( "context" + "errors" "fmt" "strconv" @@ -24,6 +25,10 @@ const ( blockResponseKey = "br" // blockIDKey is the key used for storing block IDs blockIDKey = "bid" + // lastPrunedHeightKey tracks the highest height that has been pruned. + // This makes pruning idempotent and allows incremental pruning across + // multiple calls. + lastPrunedHeightKey = "lph" ) // Store wraps a datastore with ABCI-specific functionality @@ -147,3 +152,54 @@ func (s *Store) GetBlockResponse(ctx context.Context, height uint64) (*abci.Resp return resp, nil } + +// Prune deletes per-height ABCI execution metadata (block IDs and block +// responses) for all heights up to and including the provided target +// height. The current ABCI state (stored under stateKey) is never pruned, +// as it is maintained separately by the application. +// +// Pruning is idempotent: the store tracks the highest pruned height and +// will skip work for already-pruned ranges. +func (s *Store) Prune(ctx context.Context, height uint64) error { + // Load the last pruned height, if any. + data, err := s.prefixedStore.Get(ctx, ds.NewKey(lastPrunedHeightKey)) + if err != nil { + if !errors.Is(err, ds.ErrNotFound) { + return fmt.Errorf("failed to get last pruned height: %w", err) + } + } + + var lastPruned uint64 + if len(data) > 0 { + lastPruned, err = strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("invalid last pruned height value %q: %w", string(data), err) + } + } + + // Nothing to do if we've already pruned up to at least this height. + if height <= lastPruned { + return nil + } + + // Delete per-height ABCI metadata (block IDs and block responses) for + // heights in (lastPruned, height]. Missing keys are ignored. + for h := lastPruned + 1; h <= height; h++ { + bidKey := ds.NewKey(blockIDKey).ChildString(strconv.FormatUint(h, 10)) + if err := s.prefixedStore.Delete(ctx, bidKey); err != nil && !errors.Is(err, ds.ErrNotFound) { + return fmt.Errorf("failed to delete block ID at height %d during pruning: %w", h, err) + } + + brKey := ds.NewKey(blockResponseKey).ChildString(strconv.FormatUint(h, 10)) + if err := s.prefixedStore.Delete(ctx, brKey); err != nil && !errors.Is(err, ds.ErrNotFound) { + return fmt.Errorf("failed to delete block response at height %d during pruning: %w", h, err) + } + } + + // Persist the updated last pruned height. + if err := s.prefixedStore.Put(ctx, ds.NewKey(lastPrunedHeightKey), []byte(strconv.FormatUint(height, 10))); err != nil { + return fmt.Errorf("failed to update last pruned height: %w", err) + } + + return nil +} diff --git a/pkg/store/store_test.go b/pkg/store/store_test.go index 0567383c..2b8a64f3 100644 --- a/pkg/store/store_test.go +++ b/pkg/store/store_test.go @@ -22,3 +22,56 @@ func TestStateIO(t *testing.T) { require.NoError(t, gotErr) assert.True(t, exists) } + +func TestPrune_RemovesPerHeightABCIKeysUpToTarget(t *testing.T) { + db := ds.NewMapDatastore() + absciStore := store.NewExecABCIStore(db) + + ctx := t.Context() + + // Seed per-height block ID and block response keys for heights 1..5. + for h := 1; h <= 5; h++ { + heightKey := ds.NewKey("/abci/bid").ChildString(string(rune(h + '0'))) + require.NoError(t, db.Put(ctx, heightKey, []byte("bid"))) + + respKey := ds.NewKey("/abci/br").ChildString(string(rune(h + '0'))) + require.NoError(t, db.Put(ctx, respKey, []byte("br"))) + } + + // Seed state to ensure it is not affected by pruning. + require.NoError(t, db.Put(ctx, ds.NewKey("/abci/s"), []byte("state"))) + + // Prune up to height 3. + require.NoError(t, absciStore.Prune(ctx, 3)) + + // Heights 1..3 should be deleted. + for h := 1; h <= 3; h++ { + bidKey := ds.NewKey("/abci/bid").ChildString(string(rune(h + '0'))) + exists, err := db.Has(ctx, bidKey) + require.NoError(t, err) + assert.False(t, exists) + + respKey := ds.NewKey("/abci/br").ChildString(string(rune(h + '0'))) + exists, err = db.Has(ctx, respKey) + require.NoError(t, err) + assert.False(t, exists) + } + + // Heights 4..5 should remain. + for h := 4; h <= 5; h++ { + bidKey := ds.NewKey("/abci/bid").ChildString(string(rune(h + '0'))) + exists, err := db.Has(ctx, bidKey) + require.NoError(t, err) + assert.True(t, exists) + + respKey := ds.NewKey("/abci/br").ChildString(string(rune(h + '0'))) + exists, err = db.Has(ctx, respKey) + require.NoError(t, err) + assert.True(t, exists) + } + + // State should not be pruned. + exists, err := db.Has(ctx, ds.NewKey("/abci/s")) + require.NoError(t, err) + assert.True(t, exists) +} From 9e7777d37af2415b1630ca29a8cbc0b281d9a814 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Mon, 2 Feb 2026 20:16:13 +0100 Subject: [PATCH 2/2] changed to batch delete --- pkg/store/store.go | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/pkg/store/store.go b/pkg/store/store.go index 381ee33d..3c713c59 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -182,23 +182,37 @@ func (s *Store) Prune(ctx context.Context, height uint64) error { return nil } + // Use a batch to atomically delete all per-height ABCI metadata and + // update the last pruned height in a single transaction. This avoids + // leaving the store in a partially pruned state if an error occurs + // midway through the operation. + batch, err := s.prefixedStore.Batch(ctx) + if err != nil { + return fmt.Errorf("failed to create batch for pruning: %w", err) + } + // Delete per-height ABCI metadata (block IDs and block responses) for // heights in (lastPruned, height]. Missing keys are ignored. for h := lastPruned + 1; h <= height; h++ { - bidKey := ds.NewKey(blockIDKey).ChildString(strconv.FormatUint(h, 10)) - if err := s.prefixedStore.Delete(ctx, bidKey); err != nil && !errors.Is(err, ds.ErrNotFound) { - return fmt.Errorf("failed to delete block ID at height %d during pruning: %w", h, err) + hStr := strconv.FormatUint(h, 10) + bidKey := ds.NewKey(blockIDKey).ChildString(hStr) + if err := batch.Delete(ctx, bidKey); err != nil && !errors.Is(err, ds.ErrNotFound) { + return fmt.Errorf("failed to add block ID deletion to batch at height %d: %w", h, err) } - brKey := ds.NewKey(blockResponseKey).ChildString(strconv.FormatUint(h, 10)) - if err := s.prefixedStore.Delete(ctx, brKey); err != nil && !errors.Is(err, ds.ErrNotFound) { - return fmt.Errorf("failed to delete block response at height %d during pruning: %w", h, err) + brKey := ds.NewKey(blockResponseKey).ChildString(hStr) + if err := batch.Delete(ctx, brKey); err != nil && !errors.Is(err, ds.ErrNotFound) { + return fmt.Errorf("failed to add block response deletion to batch at height %d: %w", h, err) } } - // Persist the updated last pruned height. - if err := s.prefixedStore.Put(ctx, ds.NewKey(lastPrunedHeightKey), []byte(strconv.FormatUint(height, 10))); err != nil { - return fmt.Errorf("failed to update last pruned height: %w", err) + // Persist the updated last pruned height in the same batch. + if err := batch.Put(ctx, ds.NewKey(lastPrunedHeightKey), []byte(strconv.FormatUint(height, 10))); err != nil { + return fmt.Errorf("failed to add last pruned height update to batch: %w", err) + } + + if err := batch.Commit(ctx); err != nil { + return fmt.Errorf("failed to commit pruning batch: %w", err) } return nil