Skip to content
Merged
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/single/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ replace github.com/celestiaorg/go-header => github.com/julienrbrt/go-header v0.0

replace (
github.com/evstack/ev-node => ../../../
github.com/evstack/ev-node/core => ../../../core
github.com/evstack/ev-node/da => ../../../da
github.com/evstack/ev-node/execution/evm => ../../../execution/evm
github.com/evstack/ev-node/sequencers/single => ../../../sequencers/single
Expand Down
2 changes: 0 additions & 2 deletions apps/evm/single/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ github.com/ethereum/go-ethereum v1.16.5 h1:GZI995PZkzP7ySCxEFaOPzS8+bd8NldE//1qv
github.com/ethereum/go-ethereum v1.16.5/go.mod h1:kId9vOtlYg3PZk9VwKbGlQmSACB5ESPTBGT+M9zjmok=
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-beta.3 h1:01K2Ygm3puX4m2OBxvg/HDxu+he54jeNv+KDmpgujFc=
github.com/evstack/ev-node/core v1.0.0-beta.3/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
1 change: 1 addition & 0 deletions apps/grpc/single/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ require (

replace (
github.com/evstack/ev-node => ../../../
github.com/evstack/ev-node/core => ../../../core
github.com/evstack/ev-node/da => ../../../da
github.com/evstack/ev-node/execution/grpc => ../../../execution/grpc
github.com/evstack/ev-node/sequencers/single => ../../../sequencers/single
Expand Down
2 changes: 0 additions & 2 deletions apps/grpc/single/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evstack/ev-node/core v1.0.0-beta.3 h1:01K2Ygm3puX4m2OBxvg/HDxu+he54jeNv+KDmpgujFc=
github.com/evstack/ev-node/core v1.0.0-beta.3/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY=
github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU=
github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs=
github.com/filecoin-project/go-jsonrpc v0.8.0 h1:2yqlN3Vd8Gx5UtA3fib7tQu2aW1cSOJt253LEBWExo4=
Expand Down
1 change: 1 addition & 0 deletions apps/testapp/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ replace github.com/celestiaorg/go-header => github.com/julienrbrt/go-header v0.0

replace (
github.com/evstack/ev-node => ../../.
github.com/evstack/ev-node/core => ../../core
github.com/evstack/ev-node/da => ../../da
github.com/evstack/ev-node/sequencers/single => ../../sequencers/single
)
Expand Down
2 changes: 0 additions & 2 deletions apps/testapp/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evstack/ev-node/core v1.0.0-beta.3 h1:01K2Ygm3puX4m2OBxvg/HDxu+he54jeNv+KDmpgujFc=
github.com/evstack/ev-node/core v1.0.0-beta.3/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY=
github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU=
github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs=
github.com/filecoin-project/go-jsonrpc v0.8.0 h1:2yqlN3Vd8Gx5UtA3fib7tQu2aW1cSOJt253LEBWExo4=
Expand Down
183 changes: 183 additions & 0 deletions block/internal/common/replay.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package common

import (
"bytes"
"context"
"encoding/hex"
"fmt"

"github.com/rs/zerolog"

coreexecutor "github.com/evstack/ev-node/core/execution"
"github.com/evstack/ev-node/pkg/genesis"
"github.com/evstack/ev-node/pkg/store"
"github.com/evstack/ev-node/types"
)

// Replayer handles synchronization of the execution layer with ev-node's state.
// It replays blocks from the store to bring the execution layer up to date.
type Replayer struct {
store store.Store
exec coreexecutor.Executor
genesis genesis.Genesis
logger zerolog.Logger
}

// NewReplayer creates a new execution layer replayer.
func NewReplayer(
store store.Store,
exec coreexecutor.Executor,
genesis genesis.Genesis,
logger zerolog.Logger,
) *Replayer {
return &Replayer{
store: store,
exec: exec,
genesis: genesis,
logger: logger.With().Str("component", "execution_replayer").Logger(),
}
}

// SyncToHeight checks if the execution layer is behind ev-node and syncs it to the target height.
// This is useful for crash recovery scenarios where ev-node is ahead of the execution layer.
//
// Returns:
// - error if sync fails or if execution layer is ahead of ev-node (unexpected state)
func (s *Replayer) SyncToHeight(ctx context.Context, targetHeight uint64) error {
// Check if the executor implements HeightProvider
execHeightProvider, ok := s.exec.(coreexecutor.HeightProvider)
if !ok {
s.logger.Debug().Msg("executor does not implement HeightProvider, skipping sync")
return nil
}

// Skip sync check if we're at genesis
if targetHeight < s.genesis.InitialHeight {
s.logger.Debug().Msg("at genesis height, skipping execution layer sync check")
return nil
}

execHeight, err := execHeightProvider.GetLatestHeight(ctx)
if err != nil {
return fmt.Errorf("failed to get execution layer height: %w", err)
}

s.logger.Info().
Uint64("target_height", targetHeight).
Uint64("exec_layer_height", execHeight).
Msg("execution layer height check")

// If execution layer is ahead, this is unexpected, fail hard
if execHeight > targetHeight {
s.logger.Error().
Uint64("target_height", targetHeight).
Uint64("exec_layer_height", execHeight).
Msg("execution layer is ahead of target height - this should not happen")
return fmt.Errorf("execution layer height (%d) is ahead of target height (%d)", execHeight, targetHeight)
}

// If execution layer is behind, sync the missing blocks
if execHeight < targetHeight {
s.logger.Info().
Uint64("target_height", targetHeight).
Uint64("exec_layer_height", execHeight).
Uint64("blocks_to_sync", targetHeight-execHeight).
Msg("execution layer is behind, syncing blocks")

// Sync blocks from execHeight+1 to targetHeight
for height := execHeight + 1; height <= targetHeight; height++ {
if err := s.replayBlock(ctx, height); err != nil {
return fmt.Errorf("failed to replay block %d to execution layer: %w", height, err)
}
}

s.logger.Info().
Uint64("synced_blocks", targetHeight-execHeight).
Msg("successfully synced execution layer")
} else {
s.logger.Info().Msg("execution layer is in sync")
}

return nil
}

// replayBlock replays a specific block from the store to the execution layer.
//
// Validation assumptions:
// - Blocks in the store have already been fully validated (signatures, timestamps, etc.)
// - We only verify the AppHash matches to detect state divergence
// - We skip re-validating signatures and consensus rules since this is a replay
// - This is safe because we're re-executing transactions against a known-good state
func (s *Replayer) replayBlock(ctx context.Context, height uint64) error {
s.logger.Info().Uint64("height", height).Msg("replaying block to execution layer")

// Get the block from store
header, data, err := s.store.GetBlockData(ctx, height)
if err != nil {
return fmt.Errorf("failed to get block data from store: %w", err)
}

// Get the previous state
var prevState types.State
if height == s.genesis.InitialHeight {
// For the first block, use genesis state
prevState = types.State{
ChainID: s.genesis.ChainID,
InitialHeight: s.genesis.InitialHeight,
LastBlockHeight: s.genesis.InitialHeight - 1,
LastBlockTime: s.genesis.StartTime,
AppHash: header.AppHash, // This will be updated by InitChain
}
} else {
// Get previous state from store
prevState, err = s.store.GetState(ctx)
if err != nil {
return fmt.Errorf("failed to get previous state: %w", err)
}
// We need the state at height-1, so load that block's app hash
prevHeader, _, err := s.store.GetBlockData(ctx, height-1)
if err != nil {
return fmt.Errorf("failed to get previous block header: %w", err)
}
prevState.AppHash = prevHeader.AppHash
prevState.LastBlockHeight = height - 1
}

// Prepare transactions
rawTxs := make([][]byte, len(data.Txs))
for i, tx := range data.Txs {
rawTxs[i] = []byte(tx)
}

// Execute transactions on the execution layer
s.logger.Debug().
Uint64("height", height).
Int("tx_count", len(rawTxs)).
Msg("executing transactions on execution layer")

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

// Verify the app hash matches
if !bytes.Equal(newAppHash, header.AppHash) {
err := fmt.Errorf("app hash mismatch: expected %s got %s",
hex.EncodeToString(header.AppHash),
hex.EncodeToString(newAppHash),
)
s.logger.Error().
Str("expected", hex.EncodeToString(header.AppHash)).
Str("got", hex.EncodeToString(newAppHash)).
Uint64("height", height).
Err(err).
Msg("app hash mismatch during replay")
return err
}

s.logger.Info().
Uint64("height", height).
Msg("successfully replayed block to execution layer")

return nil
}
Loading
Loading