Skip to content
Prev Previous commit
Next Next commit
remove nil checks and store setter
  • Loading branch information
tac0turtle committed Dec 18, 2025
commit ecec4e77564c887b87289b285cb3899650b77757
26 changes: 11 additions & 15 deletions apps/evm/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,23 @@ var RunCmd = &cobra.Command{
Aliases: []string{"node", "run"},
Short: "Run the evolve node with EVM execution client",
RunE: func(cmd *cobra.Command, args []string) error {
executor, err := createExecutionClient(cmd)
nodeConfig, err := rollcmd.ParseConfig(cmd)
if err != nil {
return err
}

nodeConfig, err := rollcmd.ParseConfig(cmd)
logger := rollcmd.SetupLogger(nodeConfig.Log)

// Create datastore first - needed by execution client for ExecMeta tracking
datastore, err := store.NewDefaultKVStore(nodeConfig.RootDir, nodeConfig.DBPath, evmDbName)
if err != nil {
return err
}

logger := rollcmd.SetupLogger(nodeConfig.Log)
executor, err := createExecutionClient(cmd, datastore)
if err != nil {
return err
}

blobClient, err := blobrpc.NewClient(context.Background(), nodeConfig.DA.Address, nodeConfig.DA.AuthToken, "")
if err != nil {
Expand All @@ -72,16 +78,6 @@ var RunCmd = &cobra.Command{

logger.Info().Str("headerNamespace", headerNamespace.HexString()).Str("dataNamespace", dataNamespace.HexString()).Msg("namespaces")

datastore, err := store.NewDefaultKVStore(nodeConfig.RootDir, nodeConfig.DBPath, evmDbName)
if err != nil {
return err
}

// Attach store to the EVM engine client for ExecMeta tracking (idempotent execution)
if ec, ok := executor.(*evm.EngineClient); ok {
ec.SetStore(evm.NewEVMStore(datastore))
}

genesisPath := filepath.Join(filepath.Dir(nodeConfig.ConfigPath()), "genesis.json")
genesis, err := genesispkg.LoadGenesis(genesisPath)
if err != nil {
Expand Down Expand Up @@ -210,7 +206,7 @@ func createSequencer(
return sequencer, nil
}

func createExecutionClient(cmd *cobra.Command) (execution.Executor, error) {
func createExecutionClient(cmd *cobra.Command, db datastore.Batching) (execution.Executor, error) {
// Read execution client parameters from flags
ethURL, err := cmd.Flags().GetString(evm.FlagEvmEthURL)
if err != nil {
Expand Down Expand Up @@ -255,7 +251,7 @@ func createExecutionClient(cmd *cobra.Command) (execution.Executor, error) {
genesisHash := common.HexToHash(genesisHashStr)
feeRecipient := common.HexToAddress(feeRecipientStr)

return evm.NewEngineExecutionClient(ethURL, engineURL, jwtSecret, genesisHash, feeRecipient, nil)
return evm.NewEngineExecutionClient(ethURL, engineURL, jwtSecret, genesisHash, feeRecipient, db)
}

// addFlags adds flags related to the EVM execution client
Expand Down
72 changes: 29 additions & 43 deletions execution/evm/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ type EngineClient struct {
feeRecipient common.Address // Address to receive transaction fees

// store provides persistence for ExecMeta to enable idempotent execution
// and crash recovery. Optional - if nil, ExecMeta tracking is disabled.
// and crash recovery.
store *EVMStore

mu sync.Mutex // Mutex to protect concurrent access to block hashes
Expand All @@ -161,6 +161,10 @@ func NewEngineExecutionClient(
feeRecipient common.Address,
db ds.Batching,
) (*EngineClient, error) {
if db == nil {
return nil, errors.New("db is required for EVM execution client")
}

ethClient, err := ethclient.Dial(ethURL)
if err != nil {
return nil, err
Expand Down Expand Up @@ -206,12 +210,6 @@ func (c *EngineClient) SetLogger(l zerolog.Logger) {
c.logger = l
}

// SetStore allows callers to attach a store for ExecMeta tracking.
// This enables idempotent execution and crash recovery.
func (c *EngineClient) SetStore(s *EVMStore) {
c.store = s
}

// InitChain initializes the blockchain with the given genesis parameters
func (c *EngineClient) InitChain(ctx context.Context, genesisTime time.Time, initialHeight uint64, chainID string) ([]byte, uint64, error) {
if initialHeight != 1 {
Expand Down Expand Up @@ -395,9 +393,7 @@ func (c *EngineClient) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight

// Save ExecMeta with payloadID for crash recovery (Stage="started")

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.

let's remove this nil check and assume store is always set. it is in the constructor.

// This allows resuming the payload build if we crash before completing
if c.store != nil {
c.saveExecMeta(ctx, blockHeight, timestamp.Unix(), newPayloadID[:], nil, nil, txs, ExecStageStarted)
}
c.saveExecMeta(ctx, blockHeight, timestamp.Unix(), newPayloadID[:], nil, nil, txs, ExecStageStarted)

// 4. Process the payload (get, submit, finalize)
return c.processPayload(ctx, *newPayloadID, txs)
Expand Down Expand Up @@ -623,30 +619,28 @@ func (c *EngineClient) ResumePayload(ctx context.Context, payloadIDBytes []byte)
// - found: true if either of the above is true
// - err: error during checks
func (c *EngineClient) checkIdempotency(ctx context.Context, height uint64, timestamp time.Time, txs [][]byte) (stateRoot []byte, payloadID *engine.PayloadID, found bool, err error) {
// 1. Check ExecMeta (if store is configured)
if c.store != nil {
execMeta, err := c.store.GetExecMeta(ctx, height)
if err == nil && execMeta != nil {
// If we already have a promoted block at this height, return the stored StateRoot
if execMeta.Stage == ExecStagePromoted && len(execMeta.StateRoot) > 0 {
c.logger.Info().
Uint64("height", height).
Str("stage", execMeta.Stage).
Msg("ExecuteTxs: reusing already-promoted execution (idempotent)")
return execMeta.StateRoot, nil, true, nil
}
// 1. Check ExecMeta from store
execMeta, err := c.store.GetExecMeta(ctx, height)
if err == nil && execMeta != nil {
// If we already have a promoted block at this height, return the stored StateRoot
if execMeta.Stage == ExecStagePromoted && len(execMeta.StateRoot) > 0 {
c.logger.Info().
Uint64("height", height).
Str("stage", execMeta.Stage).
Msg("ExecuteTxs: reusing already-promoted execution (idempotent)")
return execMeta.StateRoot, nil, true, nil
}

// If we have a started execution with a payloadID, return it to resume
if execMeta.Stage == ExecStageStarted && len(execMeta.PayloadID) == 8 {
c.logger.Info().
Uint64("height", height).
Str("stage", execMeta.Stage).
Msg("ExecuteTxs: found in-progress execution with payloadID, returning payloadID for resume")
// If we have a started execution with a payloadID, return it to resume
if execMeta.Stage == ExecStageStarted && len(execMeta.PayloadID) == 8 {
c.logger.Info().
Uint64("height", height).
Str("stage", execMeta.Stage).
Msg("ExecuteTxs: found in-progress execution with payloadID, returning payloadID for resume")

var pid engine.PayloadID
copy(pid[:], execMeta.PayloadID)
return nil, &pid, true, nil
}
var pid engine.PayloadID
copy(pid[:], execMeta.PayloadID)
return nil, &pid, true, nil
}
}

Expand All @@ -667,10 +661,8 @@ func (c *EngineClient) checkIdempotency(ctx context.Context, height uint64, time
// Continue anyway - the block exists and we can return its state root
}

// Update ExecMeta to promoted if store is configured
if c.store != nil {
c.saveExecMeta(ctx, height, timestamp.Unix(), nil, existingBlockHash[:], existingStateRoot.Bytes(), txs, ExecStagePromoted)
}
// Update ExecMeta to promoted
c.saveExecMeta(ctx, height, timestamp.Unix(), nil, existingBlockHash[:], existingStateRoot.Bytes(), txs, ExecStagePromoted)

return existingStateRoot.Bytes(), nil, true, nil
}
Expand Down Expand Up @@ -778,9 +770,7 @@ func (c *EngineClient) processPayload(ctx context.Context, payloadID engine.Payl
}

// 4. Save ExecMeta (Promoted)
if c.store != nil {
c.saveExecMeta(ctx, blockHeight, blockTimestamp, payloadID[:], blockHash[:], payloadResult.ExecutionPayload.StateRoot.Bytes(), txs, ExecStagePromoted)
}
c.saveExecMeta(ctx, blockHeight, blockTimestamp, payloadID[:], blockHash[:], payloadResult.ExecutionPayload.StateRoot.Bytes(), txs, ExecStagePromoted)

return payloadResult.ExecutionPayload.StateRoot.Bytes(), payloadResult.ExecutionPayload.GasUsed, nil
}
Expand All @@ -802,10 +792,6 @@ func (c *EngineClient) getBlockInfo(ctx context.Context, height uint64) (common.
// saveExecMeta persists execution metadata to the store for crash recovery.
// This is a best-effort operation - failures are logged but don't fail the execution.
func (c *EngineClient) saveExecMeta(ctx context.Context, height uint64, timestamp int64, payloadID []byte, blockHash []byte, stateRoot []byte, txs [][]byte, stage string) {
if c.store == nil {
return
}

execMeta := &ExecMeta{
Height: height,
Timestamp: timestamp,
Expand Down
3 changes: 2 additions & 1 deletion execution/evm/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package evm
import (
"context"
"encoding/binary"
"errors"
"fmt"

ds "github.com/ipfs/go-datastore"
Expand Down Expand Up @@ -105,7 +106,7 @@ func (s *EVMStore) GetExecMeta(ctx context.Context, height uint64) (*ExecMeta, e
key := execMetaKey(height)
data, err := s.db.Get(ctx, key)
if err != nil {
if err == ds.ErrNotFound {
if errors.Is(err, ds.ErrNotFound) {
return nil, nil
}
return nil, fmt.Errorf("failed to get exec meta: %w", err)
Expand Down
Loading