One node, two programs
Before September 2022, running an Ethereum node meant downloading one program — Geth, say — and that single binary did everything: it executed transactions, kept the world state, and followed proof of work to decide which block was the latest. The Merge cut that program cleanly in half. Today the node you run is two separate programs living side by side on the same machine, each a specialist, talking to each other through one narrow, locked door.
The execution client answers what happened: it runs the transactions through the EVM and computes the new world state. The consensus client answers in what order, and is it final: it runs proof of stake — who proposes the next block, which fork is canonical, when a block can never be reverted. Neither half can run the chain alone; a working node needs both, paired and in sync.
The execution client: the EVM and the world state
The execution client is the part that feels like the old node. It maintains the mempool of pending transactions, gossips them to peers over the execution-layer peer-to-peer network (transaction propagation), executes each transaction in the EVM, and updates the world state (every account balance, contract storage slot, and nonce, committed into a Merkle Patricia trie). It also serves the public-facing JSON-RPC that wallets and dApps call — `eth_call`, `eth_sendRawTransaction`, `eth_getLogs`.
Here is the crucial change The Merge brought: the execution client lost its consensus brain. It no longer decides which block is the head of the chain, and it has no concept of mining or validators. It simply executes the block payloads it is handed, re-checks them, and reports back `VALID` or `INVALID`. The decision of which payload to execute now comes from the other program.
There are several independent execution clients, each a clean-room implementation of the same spec in a different language: Geth (Go), Nethermind (C#/.NET), Besu (Java), Erigon (Go), and Reth (Rust). They must agree to the bit on every state transition; that they're written by different teams in different languages is the whole point, as we'll see at the end.
The consensus client: proof of stake and finality
The consensus client (beacon node) runs the proof-of-stake machinery. It tracks the validator set, collects their attestations (the votes validators cast each slot), runs the fork-choice rule — LMD-GHOST — to pick the current head from competing branches, and runs the finality gadget Casper FFG to finalize checkpoints. The combination of LMD-GHOST plus Casper FFG is named Gasper. Time is chopped into slots and epochs: a slot is 12 seconds (one proposal opportunity), and 32 slots make an epoch (6.4 minutes), the unit at which finality is decided.
Crucially, the beacon node knows nothing about the EVM. To it, an execution payload is an opaque blob: it carries it around, hands it to the execution client to validate, and remembers only the resulting block hash. Like the EL, the CL comes in several implementations — Lighthouse (Rust), Prysm (Go), Teku (Java), Nimbus (Nim), and Lodestar (TypeScript).
The Engine API: the door between them
The two clients run on the same box and talk over the Engine API — a small, authenticated JSON-RPC interface, conventionally on port 8551, completely separate from the public RPC on 8545. The door is locked with a shared JWT secret: a 32-byte HS256 key written to a `jwtsecret` file that both programs read. Without that secret the Engine API rejects every request — which stops a rogue process (or a hostile website hitting localhost) from feeding your node forged blocks.
Only three core methods carry the whole conversation. `engine_newPayloadV3` means "here is a block — execute and validate it." `engine_forkchoiceUpdatedV3` means "this is the new head / safe / finalized block — and, optionally, start building a block for me." `engine_getPayloadV3` means "give me the block you built." The CL always speaks; the EL always answers.
# 1. Create the shared JWT secret once: 32 random bytes, hex-encoded openssl rand -hex 32 > /secrets/jwt.hex # 2. Execution client (Geth): serve the Engine API on :8551, locked by the JWT. # The public JSON-RPC for dApps stays on :8545. geth --mainnet \ --authrpc.addr 127.0.0.1 --authrpc.port 8551 \ --authrpc.jwtsecret /secrets/jwt.hex \ --http --http.api eth,net,web3 # 3. Consensus client (Lighthouse beacon node): point it at the EL's # Engine endpoint, presenting the SAME secret. Mismatch = no connection. lighthouse bn --network mainnet \ --execution-endpoint http://127.0.0.1:8551 \ --execution-jwt /secrets/jwt.hex \ --checkpoint-sync-url https://mainnet.checkpoint.sigp.io
- Following the chain (your node is NOT proposing): the beacon node receives a new beacon block from a peer over the consensus gossip network.
- It extracts the embedded execution payload — the EVM-level block of transactions and the claimed new state root — and calls engine_newPayloadV3 to hand it to the execution client.
- The execution client re-runs every transaction, recomputes the state root, and replies VALID, INVALID, or SYNCING. INVALID means the block is rejected and never attested to.
- The beacon node folds the result into LMD-GHOST fork choice, then calls engine_forkchoiceUpdatedV3 to tell the EL the new head, safe, and finalized block hashes.
- Proposing a block (your validator's turn): the CL calls engine_forkchoiceUpdatedV3 WITH payload attributes (timestamp, fee recipient, withdrawals), telling the EL to start building a block from the mempool.
- A moment later the CL calls engine_getPayloadV3 to collect the finished payload, wraps it in a beacon block, has the validator client sign it, and broadcasts it to peers.
Why split a node in two?
This is separation of concerns at the protocol level. The consensus rules can evolve — moving toward single-slot finality, say — without touching the EVM, and the execution rules can change without touching consensus. Because the Engine API is a stable contract, you can mix and match: any of the five execution clients with any of the five consensus clients, 25 combinations, all interoperable. Independent teams, independent codebases, independent bugs.
Be honest about the cost, though: a node is now more moving parts. Two programs, two on-disk databases, two sync processes, an extra port, and a JWT to provision. Both halves must be up and in step — if the EL is still syncing, the CL reports `SYNCING` and your node can't attest; if the JWT mismatches, the two simply refuse to talk. The modularity is worth it, but the operational surface is genuinely larger than the old single binary.
Client diversity: the network's quiet systemic risk
Because every client implements the same spec, a bug in one of them is contained — as long as that client isn't too large a share of the network. Client diversity is precisely this safety margin, and the danger lives at two thresholds: one-third and two-thirds of the staked validators.
Finality requires two-thirds of stake to attest to the same chain. So if a buggy client controls more than 1/3 and it crashes or forks off, the rest can't reach the two-thirds bar: the chain keeps producing blocks but stops finalizing, and the inactivity leak slowly drains the stuck side until finality resumes — disruptive, but safe and recoverable. The catastrophe is more than 2/3: a single buggy client that big could attest to and finalize an invalid block. The honest minority (under 1/3) cannot revert a finalized block, so recovery would demand a socially-coordinated fork that slashes the supermajority — potentially destroying a third or more of all stake. That is why the community's rule of thumb is blunt: no single execution or consensus client above 33%.
This is not theoretical. Around The Merge, Geth held roughly 80%+ of execution-client share — a single consensus bug in it could have split the chain — and on the consensus side Prysm repeatedly drifted above one-third. The real stress test came in May 2023: for several epochs the chain produced blocks but failed to finalize, traced to consensus clients (Prysm and Teku) choking under load while processing a flood of valid-but-old attestations. Because not every validator ran the same client, the unaffected clients carried the network and finalization resumed within minutes. A monoculture might not have recovered so gently.