Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/evm/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ module github.com/evstack/ev-node/apps/evm

go 1.25.0

replace (
github.com/evstack/ev-node => ../../
github.com/evstack/ev-node/core => ../../core
github.com/evstack/ev-node/execution/evm => ../../execution/evm
)

require (
github.com/celestiaorg/go-header v0.8.1
github.com/ethereum/go-ethereum v1.16.8
Expand Down
6 changes: 0 additions & 6 deletions apps/evm/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -409,12 +409,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 v1.0.0-rc.1 h1:MO7DT3y1X4WK7pTgl/867NroqhXJ/oe2NbmvMr3jqq8=
github.com/evstack/ev-node v1.0.0-rc.1/go.mod h1:JtbvY2r6k6ZhGYMeDNZk7cx6ALj3d0f6dVyyJmJHBd4=
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/evstack/ev-node/execution/evm v1.0.0-rc.1 h1:CrjlRI6hufue3KozvDuKP14gLwFvnOmXfGEJIszGEcQ=
github.com/evstack/ev-node/execution/evm v1.0.0-rc.1/go.mod h1:GUxGZgS9F4w6DOcS5gEdW1h71IdAGdaY8C1urSOkpUQ=
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=
Expand Down
6 changes: 6 additions & 0 deletions apps/grpc/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ module github.com/evstack/ev-node/apps/grpc

go 1.25.0

replace (
github.com/evstack/ev-node => ../../
github.com/evstack/ev-node/core => ../../core
github.com/evstack/ev-node/execution/grpc => ../../execution/grpc
)

require (
github.com/evstack/ev-node v1.0.0-rc.1
github.com/evstack/ev-node/core v1.0.0-rc.1
Expand Down
6 changes: 0 additions & 6 deletions apps/grpc/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -365,12 +365,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.1 h1:MO7DT3y1X4WK7pTgl/867NroqhXJ/oe2NbmvMr3jqq8=
github.com/evstack/ev-node v1.0.0-rc.1/go.mod h1:JtbvY2r6k6ZhGYMeDNZk7cx6ALj3d0f6dVyyJmJHBd4=
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/evstack/ev-node/execution/grpc v1.0.0-rc.1 h1:OzrWLDDY6/9+LWx0XmUqPzxs/CHZRJICOwQ0Me/i6dY=
github.com/evstack/ev-node/execution/grpc v1.0.0-rc.1/go.mod h1:Pr/sF6Zx8am9ZeWFcoz1jYPs0kXmf+OmL8Tz2Gyq7E4=
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 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
Expand Down
5 changes: 5 additions & 0 deletions apps/testapp/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ module github.com/evstack/ev-node/apps/testapp

go 1.25.0

replace (
github.com/evstack/ev-node => ../../.
github.com/evstack/ev-node/core => ../../core
)

require (
github.com/celestiaorg/go-header v0.8.1
github.com/evstack/ev-node v1.0.0-rc.1
Expand Down
4 changes: 0 additions & 4 deletions apps/testapp/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -365,10 +365,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.1 h1:MO7DT3y1X4WK7pTgl/867NroqhXJ/oe2NbmvMr3jqq8=
github.com/evstack/ev-node v1.0.0-rc.1/go.mod h1:JtbvY2r6k6ZhGYMeDNZk7cx6ALj3d0f6dVyyJmJHBd4=
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 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
Expand Down
28 changes: 28 additions & 0 deletions block/internal/executing/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,34 @@ 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")
}

// 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")
}
}
}
}
}

// broadcast header and data to P2P network
g, broadcastCtx := errgroup.WithContext(ctx)
g.Go(func() error { return e.headerBroadcaster.WriteToStoreAndBroadcast(broadcastCtx, header) })
Expand Down
13 changes: 13 additions & 0 deletions core/execution/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
17 changes: 17 additions & 0 deletions execution/evm/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions execution/evm/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
4 changes: 0 additions & 4 deletions execution/evm/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +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=
github.com/evstack/ev-node v1.0.0-rc.1 h1:MO7DT3y1X4WK7pTgl/867NroqhXJ/oe2NbmvMr3jqq8=
github.com/evstack/ev-node v1.0.0-rc.1/go.mod h1:JtbvY2r6k6ZhGYMeDNZk7cx6ALj3d0f6dVyyJmJHBd4=
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/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=
Expand Down
53 changes: 53 additions & 0 deletions execution/evm/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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))
Expand Down
99 changes: 99 additions & 0 deletions execution/evm/store_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,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
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,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 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
Expand Down
Loading
Loading