This repository implements the execution.Executor interface from github.com/evstack/ev-node/core/execution (currently on feature branch feature/exec_api). It provides a pure Engine API-based execution client for Evolve.
The EngineClient is a 100% Engine API compatible implementation of the execution.Executor interface. It connects to an Ethereum execution client (like Reth) and uses both the Engine API and standard Ethereum JSON-RPC API to execute transactions.
In the context of the EVM, the genesis block is designated as a unique block with the block number 0.
To ensure compatibility with both evolve and the EVM, the only permissible initial height is 1.
During InitChain EVM genesis block is acknowledged (at height 0), and empty block is created (at height 1).
This approach ensures that the block numbers between the EVM and evolve remain consistent and synchronized.
Since the PureEngineClient relies on the Engine API, the genesis configuration must properly enable it with the correct hardfork settings:
- The genesis file must include post-merge hardfork configurations
terminalTotalDifficultymust be set to 0terminalTotalDifficultyPassedmust be set to true- Hardforks like
mergeNetsplitBlock,shanghaiTime,cancunTime,pragueTime, and optionallyamsterdamTimeshould be properly configured
Example of required genesis configuration:
{
"config": {
"chainId": 1234,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"mergeNetsplitBlock": 0,
"terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true,
"shanghaiTime": 0,
"cancunTime": 0,
"pragueTime": 0,
"amsterdamTime": 0
}
}Without these settings, the Engine API will not be available, and the PureEngineClient will not function correctly.
ev-node uses fork-aware Engine API methods:
- Prague payloads use
engine_forkchoiceUpdatedV3,engine_getPayloadV4, andengine_newPayloadV4. - Osaka/Fusaka payloads fall forward from
engine_getPayloadV4toengine_getPayloadV5on unsupported-fork responses. - Amsterdam payloads use
engine_forkchoiceUpdatedV4,engine_getPayloadV6, andengine_newPayloadV5.
ev-node auto-detects Engine API versions by retrying on unsupported-fork responses. For payload builds, it first tries engine_forkchoiceUpdatedV3; if the execution layer rejects that method for Amsterdam, ev-node retries engine_forkchoiceUpdatedV4 with a deterministic slotNumber derived from rollup block height and caches V4 for future calls.
Amsterdam adds slotNumber to payload attributes and executionPayload.blockAccessList to built payloads. ev-node does not compute the block access list; it preserves the raw executionPayload returned by ev-reth and submits that object unchanged to engine_newPayloadV5.
The PureEngineClient maintains the payloadID between calls:
- During
InitChain, the genesis forkchoice is acknowledged through the Engine API - This payload ID is stored in the client instance as
c.payloadID - The stored payload ID is used in subsequent calls to
GetTxsto retrieve the current execution payload - After each
ExecuteTxscall, a new payload ID is obtained and stored for the next block
The PureEngineClient implements a unique approach to transaction execution:
- In
GetTxs, the entire execution payload is serialized to JSON and returned as the first transaction - In
ExecuteTxs, this first transaction is deserialized back into an execution payload - The remaining transactions are added to the payload's transaction list
- The complete payload is then submitted to the execution client via the fork-appropriate
engine_newPayloadmethod
This approach ensures that:
- The execution payload structure is preserved between calls
- All execution happens within the EVM
- It's not possible to create a payload outside of the EVM
- Transactions cannot be selected or ordered outside of the EVM
The PureEngineClient uses the standard Ethereum JSON-RPC API for:
- Retrieving block information (via
HeaderByNumber) - Reading the genesis block hash and state root
- Getting gas limits and other block parameters
This allows the client to interact with the execution layer for read operations while using the Engine API for write operations.
graph LR
subgraph Evolve Binary
EvolveCore[Evolve Core]
PureEngineClient[PureEngineClient]
end
%% Connections
EvolveCore --> PureEngineClient
PureEngineClient -->|Engine API| Reth
PureEngineClient -->|Eth API| Reth
cd docker
docker compose up -dIf you've modified the genesis file, you can read the genesis hash and state root using the Ethereum JSON-RPC API:
# Get genesis block hash
curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x0", false],"id":1}' http://localhost:8545 | jq -r '.result.hash'
# Get genesis state root
curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x0", false],"id":1}' http://localhost:8545 | jq -r '.result.stateRoot'