Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
da7b970
add key rotation
tac0turtle Apr 23, 2026
6a0f367
remove need for pubkey and add some tests
tac0turtle Apr 23, 2026
6ad13f2
comments and amendments
tac0turtle Apr 23, 2026
45a7d75
commen changes
tac0turtle Apr 24, 2026
8db31f9
redo the design
tac0turtle Apr 24, 2026
6b2db4b
revert header change
tac0turtle Apr 24, 2026
656944d
Merge branch 'main' into marko/key_rotation
tac0turtle Apr 27, 2026
2fbd381
deps
Apr 27, 2026
e505b36
fix docker and proto
Apr 27, 2026
7b4011c
ammendments from commnets
Apr 27, 2026
68997e7
linting
Apr 27, 2026
04f6b12
test: cover unexpected DA proposer rejection
randygrok May 5, 2026
0b7bb5a
fix: resolve key rotation conflicts with main (#3313)
randygrok May 8, 2026
da43b26
Merge branch 'main' into marko/key_rotation
julienrbrt May 8, 2026
e8ce3b9
fix merge
julienrbrt May 8, 2026
c811388
Merge branch 'main' into marko/key_rotation
randygrok May 12, 2026
3d349ed
fix: classify invalid external blocks safely
randygrok May 13, 2026
de542e2
chore: update EVM module tidy output
randygrok May 13, 2026
ea805f7
ci: retry Docker image pulls in E2E tests
randygrok May 13, 2026
6686e6a
fix: preserve DA data until block acceptance
randygrok May 13, 2026
a5bac4e
Merge branch 'main' into marko/key_rotation
randygrok May 19, 2026
2b53162
Merge remote-tracking branch 'origin/main' into marko/key_rotation
randygrok May 21, 2026
e5537b8
fix: selectively evict header or data from cache on block validation …
randygrok May 21, 2026
b6baa26
feat: handle execution proposer rotation
randygrok May 27, 2026
bd2c237
test: require proposer-control image for rotation e2e
randygrok May 27, 2026
31d3993
Merge remote-tracking branch 'origin/main' into marko/key_rotation
randygrok May 27, 2026
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
73 changes: 73 additions & 0 deletions .github/workflows/docker-tests.yml
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

i guess these could be reverted?

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
docker-tests:
permissions:
contents: read
packages: read
name: Docker E2E Tests
runs-on: ubuntu-latest
steps:
Expand All @@ -30,6 +31,28 @@
cache-dependency-path: "**/go.sum"
- name: Install just
uses: extractions/setup-just@v4
- name: Log in to GHCR
uses: docker/login-action@v3

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow or composite action Medium

Unpinned 3rd party Action 'Docker E2E Tests' step
Uses Step
uses 'docker/login-action' with ref 'v3', not a pinned commit hash
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pre-pull Docker images
run: |
pull_with_retry() {
local image="$1"
for attempt in 1 2 3 4 5; do
if docker pull "$image"; then
return 0
fi
sleep $((attempt * 10))
done
docker pull "$image"
}

pull_with_retry ghcr.io/celestiaorg/celestia-app:v5.0.2
pull_with_retry ghcr.io/celestiaorg/celestia-node:v0.25.3
pull_with_retry ghcr.io/${{ github.repository_owner }}/ev-node-testapp:${{ inputs.image-tag }}
- name: Run Docker E2E Tests
run: just test-docker-e2e
env:
Expand All @@ -40,6 +63,7 @@
name: Docker Upgrade E2E Tests
permissions:
contents: read
packages: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
Expand All @@ -50,6 +74,30 @@
cache-dependency-path: "**/go.sum"
- name: Install just
uses: extractions/setup-just@v4
- name: Log in to GHCR
uses: docker/login-action@v3

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow or composite action Medium

Unpinned 3rd party Action 'Docker E2E Tests' step
Uses Step
uses 'docker/login-action' with ref 'v3', not a pinned commit hash
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pre-pull Docker images
run: |
pull_with_retry() {
local image="$1"
for attempt in 1 2 3 4 5; do
if docker pull "$image"; then
return 0
fi
sleep $((attempt * 10))
done
docker pull "$image"
}

pull_with_retry ghcr.io/celestiaorg/celestia-app:v5.0.2
pull_with_retry ghcr.io/celestiaorg/celestia-node:v0.25.3
pull_with_retry ghcr.io/evstack/ev-reth:v0.2.2
pull_with_retry ghcr.io/evstack/ev-node-evm:main
pull_with_retry ghcr.io/${{ github.repository_owner }}/ev-node-evm:${{ inputs.image-tag }}
- name: Run Docker Upgrade E2E Tests
run: just test-docker-upgrade-e2e
env:
Expand All @@ -60,6 +108,7 @@
name: Docker Compatibility E2E Tests
permissions:
contents: read
packages: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
Expand All @@ -70,6 +119,30 @@
cache-dependency-path: "**/go.sum"
- name: Install just
uses: extractions/setup-just@v4
- name: Log in to GHCR
uses: docker/login-action@v3

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow or composite action Medium

Unpinned 3rd party Action 'Docker E2E Tests' step
Uses Step
uses 'docker/login-action' with ref 'v3', not a pinned commit hash
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pre-pull Docker images
run: |
pull_with_retry() {
local image="$1"
for attempt in 1 2 3 4 5; do
if docker pull "$image"; then
return 0
fi
sleep $((attempt * 10))
done
docker pull "$image"
}

pull_with_retry ghcr.io/celestiaorg/celestia-app:v5.0.2
pull_with_retry ghcr.io/celestiaorg/celestia-node:v0.25.3
pull_with_retry ghcr.io/evstack/ev-reth:v0.2.2
pull_with_retry ghcr.io/${{ github.repository_owner }}/ev-node-evm:main
pull_with_retry ghcr.io/${{ github.repository_owner }}/ev-node-evm:${{ inputs.image-tag }}
- name: Run Docker Compat E2E Tests
run: just test-docker-compat
env:
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/proto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,17 @@ jobs:
- uses: bufbuild/buf-action@v1
with:
format: false
breaking: false
- name: Check protobuf breaking changes
env:
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }}
run: |
if [ -z "$BASE_SHA" ] || [[ "$BASE_SHA" =~ ^0+$ ]]; then
echo "No base SHA available for buf breaking check"
exit 0
fi

buf breaking proto \
--limit-to-input-files \
--error-format github-actions \
--against "https://github.com/${{ github.repository }}.git#format=git,commit=${BASE_SHA}"
1 change: 1 addition & 0 deletions apps/evm/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.25.8

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

Expand Down
2 changes: 0 additions & 2 deletions apps/evm/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,6 @@ github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJ
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
github.com/ethereum/go-ethereum v1.17.3 h1:Ev/sQHH+UdKZHWjuVzhu2pxhi/sXaPZl23Q+Q5LDd4Q=
github.com/ethereum/go-ethereum v1.17.3/go.mod h1:f2EhRwqewIZkGoQekywI2Y2RZAMTSavLNkD9qItFy1A=
github.com/evstack/ev-node/core v1.0.0 h1:s0Tx0uWHme7SJn/ZNEtee4qNM8UO6PIxXnHhPbbKTz8=
github.com/evstack/ev-node/core v1.0.0/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
Expand Down
1 change: 1 addition & 0 deletions apps/grpc/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.25.8

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

Expand Down
2 changes: 0 additions & 2 deletions apps/grpc/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,6 @@ github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQ
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
github.com/evstack/ev-node/core v1.0.0 h1:s0Tx0uWHme7SJn/ZNEtee4qNM8UO6PIxXnHhPbbKTz8=
github.com/evstack/ev-node/core v1.0.0/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
Expand Down
1 change: 1 addition & 0 deletions apps/testapp/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ WORKDIR /ev-node
# Dependencies are only re-downloaded when go.mod or go.sum change.
COPY go.mod go.sum ./
COPY apps/testapp/go.mod apps/testapp/go.sum ./apps/testapp/
COPY core/go.mod core/go.sum ./core/
RUN go mod download && (cd apps/testapp && go mod download)

# Copy the rest of the source and build.
Expand Down
5 changes: 4 additions & 1 deletion apps/testapp/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ module github.com/evstack/ev-node/apps/testapp

go 1.25.8

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

require (
github.com/evstack/ev-node v1.1.1
Expand Down
2 changes: 0 additions & 2 deletions apps/testapp/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,6 @@ github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQ
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
github.com/evstack/ev-node/core v1.0.0 h1:s0Tx0uWHme7SJn/ZNEtee4qNM8UO6PIxXnHhPbbKTz8=
github.com/evstack/ev-node/core v1.0.0/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
Expand Down
14 changes: 7 additions & 7 deletions apps/testapp/kv/kvexecutor.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,16 +239,16 @@ func (k *KVExecutor) GetTxs(ctx context.Context) ([][]byte, error) {
// ExecuteTxs processes each transaction assumed to be in the format "key=value".
// It updates the database accordingly using a batch and removes the executed transactions from the mempool.
// Invalid transactions are filtered out and logged, but execution continues.
func (k *KVExecutor) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight uint64, timestamp time.Time, prevStateRoot []byte) ([]byte, error) {
func (k *KVExecutor) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight uint64, timestamp time.Time, prevStateRoot []byte) (execution.ExecuteResult, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
return execution.ExecuteResult{}, ctx.Err()
default:
}

batch, err := k.db.Batch(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create database batch: %w", err)
return execution.ExecuteResult{}, fmt.Errorf("failed to create database batch: %w", err)
}

validTxCount := 0
Expand Down Expand Up @@ -291,7 +291,7 @@ func (k *KVExecutor) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight u
err = batch.Put(ctx, dsKey, []byte(value))
if err != nil {
// This error is unlikely for Put unless the context is cancelled.
return nil, fmt.Errorf("failed to stage put operation in batch for key '%s': %w", key, err)
return execution.ExecuteResult{}, fmt.Errorf("failed to stage put operation in batch for key '%s': %w", key, err)
}
validTxCount++
}
Expand All @@ -304,7 +304,7 @@ func (k *KVExecutor) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight u
// Commit the batch to apply all changes atomically
err = batch.Commit(ctx)
if err != nil {
return nil, fmt.Errorf("failed to commit transaction batch: %w", err)
return execution.ExecuteResult{}, fmt.Errorf("failed to commit transaction batch: %w", err)
}

k.blocksProduced.Add(1)
Expand All @@ -315,10 +315,10 @@ func (k *KVExecutor) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight u
if err != nil {
// This is problematic, state was changed but root calculation failed.
// May need more robust error handling or recovery logic.
return nil, fmt.Errorf("failed to compute state root after executing transactions: %w", err)
return execution.ExecuteResult{}, fmt.Errorf("failed to compute state root after executing transactions: %w", err)
}

return stateRoot, nil
return execution.ExecuteResult{UpdatedStateRoot: stateRoot}, nil
}

// SetFinal marks a block as finalized at the specified height.
Expand Down
12 changes: 6 additions & 6 deletions apps/testapp/kv/kvexecutor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ func TestExecuteTxs_Valid(t *testing.T) {
[]byte("key2=value2"),
}

stateRoot, err := exec.ExecuteTxs(ctx, txs, 1, time.Now(), []byte(""))
result, err := exec.ExecuteTxs(ctx, txs, 1, time.Now(), []byte(""))
if err != nil {
t.Fatalf("ExecuteTxs failed: %v", err)
}

// Check that stateRoot contains the updated key-value pairs
rootStr := string(stateRoot)
rootStr := string(result.UpdatedStateRoot)
if !strings.Contains(rootStr, "key1:value1;") || !strings.Contains(rootStr, "key2:value2;") {
t.Errorf("State root does not contain expected key-values: %s", rootStr)
}
Expand All @@ -134,13 +134,13 @@ func TestExecuteTxs_Invalid(t *testing.T) {
[]byte(""),
}

stateRoot, err := exec.ExecuteTxs(ctx, txs, 1, time.Now(), []byte(""))
result, err := exec.ExecuteTxs(ctx, txs, 1, time.Now(), []byte(""))
if err != nil {
t.Fatalf("ExecuteTxs should handle gibberish gracefully, got error: %v", err)
}

// State root should still be computed (empty block is valid)
if stateRoot == nil {
if result.UpdatedStateRoot == nil {
t.Error("Expected non-nil state root even with all invalid transactions")
}

Expand All @@ -152,13 +152,13 @@ func TestExecuteTxs_Invalid(t *testing.T) {
[]byte(""),
}

stateRoot2, err := exec.ExecuteTxs(ctx, mixedTxs, 2, time.Now(), stateRoot)
result2, err := exec.ExecuteTxs(ctx, mixedTxs, 2, time.Now(), result.UpdatedStateRoot)
if err != nil {
t.Fatalf("ExecuteTxs should filter invalid transactions and process valid ones, got error: %v", err)
}

// State root should contain only the valid transactions
rootStr := string(stateRoot2)
rootStr := string(result2.UpdatedStateRoot)
if !strings.Contains(rootStr, "valid_key:valid_value") || !strings.Contains(rootStr, "another_valid:value2") {
t.Errorf("State root should contain valid transactions: %s", rootStr)
}
Expand Down
41 changes: 28 additions & 13 deletions block/internal/common/replay.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,19 @@ func (s *Replayer) replayBlock(ctx context.Context, height uint64) error {
// Get the previous state
var prevState types.State
if height == s.genesis.InitialHeight {
// For the first block, use genesis state.
// For the first block, use genesis state. Mirror Syncer.initializeState():
// prefer the execution layer's view of the next proposer, fall back to genesis.
nextProposer := append([]byte(nil), s.genesis.ProposerAddress...)
if info, infoErr := s.exec.GetExecutionInfo(ctx); infoErr == nil && len(info.NextProposerAddress) > 0 {
nextProposer = append([]byte(nil), info.NextProposerAddress...)
}
prevState = types.State{
ChainID: s.genesis.ChainID,
InitialHeight: s.genesis.InitialHeight,
LastBlockHeight: s.genesis.InitialHeight - 1,
LastBlockTime: s.genesis.StartTime,
AppHash: header.AppHash, // Genesis app hash (input to first block execution)
ChainID: s.genesis.ChainID,
InitialHeight: s.genesis.InitialHeight,
LastBlockHeight: s.genesis.InitialHeight - 1,
LastBlockTime: s.genesis.StartTime,
AppHash: header.AppHash, // Genesis app hash (input to first block execution)
NextProposerAddress: nextProposer,
}
} else {
// GetStateAtHeight(height-1) returns the state AFTER block height-1 was executed,
Expand All @@ -179,10 +185,16 @@ func (s *Replayer) replayBlock(ctx context.Context, height uint64) error {
Int("tx_count", len(rawTxs)).
Msg("executing transactions on execution layer")

newAppHash, err := s.exec.ExecuteTxs(ctx, rawTxs, height, header.Time(), prevState.AppHash)
result, err := s.exec.ExecuteTxs(ctx, rawTxs, height, header.Time(), prevState.AppHash)
if err != nil {
return fmt.Errorf("failed to execute transactions: %w", err)
}
newAppHash := result.UpdatedStateRoot

newState, err := prevState.NextState(header.Header, newAppHash, result.NextProposerAddress)
if err != nil {
return fmt.Errorf("calculate next state: %w", err)
}

// The result of ExecuteTxs (newAppHash) should match the stored state at this height.
// Note: header.AppHash is the PREVIOUS state's app hash (input), not the expected output.
Expand All @@ -207,6 +219,15 @@ func (s *Replayer) replayBlock(ctx context.Context, height uint64) error {
Msg("app hash mismatch during replay")
return err
}
if len(expectedState.NextProposerAddress) > 0 {
if !bytes.Equal(newState.NextProposerAddress, expectedState.NextProposerAddress) {
return fmt.Errorf("next proposer mismatch at height %d: expected %x got %x",
height,
expectedState.NextProposerAddress,
newState.NextProposerAddress,
)
}
}
s.logger.Debug().
Uint64("height", height).
Str("app_hash", hex.EncodeToString(newAppHash)).
Expand All @@ -219,12 +240,6 @@ func (s *Replayer) replayBlock(ctx context.Context, height uint64) error {
Msg("replayBlock: ExecuteTxs completed (no stored state to verify against)")
}

// Calculate new state
newState, err := prevState.NextState(header.Header, newAppHash)
if err != nil {
return fmt.Errorf("calculate next state: %w", err)
}

// Persist the new state
batch, err := s.store.NewBatch(ctx)
if err != nil {
Expand Down
Loading
Loading