Skip to content
Open
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
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.7

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.2 h1:ag6geu0kn8Hv5FLKTpH+Hm2DHD+iuFtuqKxEuwUsDOI=
github.com/ethereum/go-ethereum v1.17.2/go.mod h1:KHcRXfGOUfUmKg51IhQ0IowiqZ6PqZf08CMtk0g5K1o=
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.7

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.36.0/go.mod h1:ty89S1YCCVruQAm9O
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
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
@@ -1,7 +1,7 @@
FROM golang:1.26 AS base

#hadolint ignore=DL3018
RUN apt-get update && \

Check failure on line 4 in apps/testapp/Dockerfile

View workflow job for this annotation

GitHub Actions / lint / hadolint

DL3008 warning: Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
Expand All @@ -21,7 +21,8 @@
# 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)

Check failure on line 25 in apps/testapp/Dockerfile

View workflow job for this annotation

GitHub Actions / lint / hadolint

DL3003 warning: Use WORKDIR to switch to a directory

# Copy the rest of the source and build.
COPY . .
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.7

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.36.0/go.mod h1:ty89S1YCCVruQAm9O
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
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