Skip to content

cl, engineapi: encode client versions in default block graffiti - #22303

Merged
lystopad merged 5 commits into
mainfrom
feature/lystopad/caplin-graffiti-client-version
Jul 8, 2026
Merged

lystopad merged 5 commits into
mainfrom
feature/lystopad/caplin-graffiti-client-version

Conversation

@lystopad

@lystopad lystopad commented Jul 7, 2026

Copy link
Copy Markdown
Member

What

When a validator proposes a block without specifying graffiti, Caplin now fills the graffiti with the client-version encoding from the client identification standard instead of the literal "Caplin" string:

<EL code><EL commit><CN><CL commit>    e.g. EGa53eCNa53e

This lets client-diversity tooling attribute proposed blocks to both their execution and consensus clients. Today an Erigon + Caplin node leaves no execution-client fingerprint in its blocks.

Why

Execution-layer client diversity is largely unmeasurable from block data. The standard addresses this by having the consensus client embed the execution and consensus client codes and their commit prefixes in the default graffiti. Caplin previously emitted only the literal "Caplin", contributing no execution-client signal.

How

  • Adds GetClientVersionV1 to the ExecutionEngine interface; Caplin obtains the execution client's code and commit via engine_getClientVersionV1. The result is cached, so steady-state block production performs no extra engine API calls.
  • Caplin identifies itself with the reserved consensus client code CN.
  • When the execution client does not support the method, the graffiti degrades to the consensus-client identifier only (CN<commit>).
  • User-specified graffiti is unchanged.
  • Deduplicates the local client-version construction into engine_types.NewClientVersionV1 / LocalClientVersionV1, now shared by the engine API handler and the in-process execution client.

Dependency

Caplin's consensus client code CN is registered in ethereum/execution-apis#844. This change can merge independently: the encoding works before registration (consumers must accept any two-letter code), and attribution tooling recognizes CN once that PR lands.

Testing

  • New cl/beacon/handler/block_production_graffiti_test.go: encoding, execution-client-unavailable fallback, no-engine fallback, and cache behavior (the engine is queried once across multiple proposals).
  • make lint, go vet, and the affected package tests pass.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates Caplin’s default block graffiti to follow the Engine API client-identification standard so proposed blocks can be attributed to both the execution (EL) and consensus (CL) clients, and wires Caplin to query engine_getClientVersionV1 to obtain the EL identification (with caching).

Changes:

  • Adds GetClientVersionV1 to Caplin’s ExecutionEngine interface and implements it across real/mocked engines.
  • Introduces shared helpers (NewClientVersionV1 / LocalClientVersionV1) to construct ClientVersionV1 consistently.
  • Replaces Caplin’s default graffiti generation with standard <EL code><EL commit><CL code><CL commit> encoding and adds targeted tests.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
execution/engineapi/engine_types/jsonrpc.go Adds helpers to construct ClientVersionV1 (including local node version).
execution/engineapi/engine_api_methods.go Simplifies GetClientVersionV1 handler to reuse the shared local helper.
cl/phase1/execution_client/interface.go Extends the CL↔EL interface with GetClientVersionV1.
cl/phase1/execution_client/execution_engine_mock.go Updates gomock generation to include GetClientVersionV1.
cl/phase1/execution_client/execution_client_engine.go Plumbs GetClientVersionV1 through the engine-backed execution client.
cl/phase1/execution_client/execution_client_direct.go Implements GetClientVersionV1 for in-process Erigon (direct mode).
cl/beacon/handler/handler.go Adds an atomic cached pointer for EL client version used in default graffiti.
cl/beacon/handler/block_production.go Implements standard default-graffiti encoding and EL version lookup/caching.
cl/beacon/handler/block_production_graffiti_test.go Adds tests covering encoding, fallbacks, and caching behavior.
cl/spectest/consensus_tests/fork_choice.go Updates spectest engine stub to satisfy the new interface method.
cl/phase1/stages/gloas_payload_test.go Updates test execution engine stub to satisfy the new interface method.
Files not reviewed (1)
  • cl/phase1/execution_client/execution_engine_mock.go: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cl/beacon/handler/block_production.go Outdated
@awskii
awskii requested a review from Copilot July 8, 2026 05:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • cl/phase1/execution_client/execution_engine_mock.go: Generated file

Comment thread cl/beacon/handler/block_production.go
Comment thread execution/engineapi/engine_types/jsonrpc.go
@lystopad lystopad added the Caplin Caplin: Consensus Layer, Beacon API label Jul 8, 2026
@lystopad
lystopad requested a review from Copilot July 8, 2026 06:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • cl/phase1/execution_client/execution_engine_mock.go: Generated file

Comment thread cl/beacon/handler/block_production.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • cl/phase1/execution_client/execution_engine_mock.go: Generated file

Comment thread cl/beacon/handler/block_production.go Outdated
Comment on lines +99 to +105
func (a *ApiHandler) defaultGraffiti(ctx context.Context) common.Hash {
graffiti := caplinClientCode + graffitiCommitPrefix(version.GitCommit)
if el := a.executionClientVersion(ctx); el != nil {
graffiti = el.Code + graffitiCommitPrefix(el.Commit) + graffiti
}
return graffitiFromString(graffiti)
}
Comment thread cl/beacon/handler/block_production.go Outdated
Comment on lines +136 to +139
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
caplin := engine_types.NewClientVersionV1(caplinClientCode, caplinClientName, a.version, version.GitCommit)
versions, err := a.engine.GetClientVersionV1(ctx, &caplin)
Comment thread execution/engineapi/engine_api_methods.go
Comment on lines +185 to +187
// NewClientVersionV1 builds a ClientVersionV1 from a git commit hash, truncating
// it to the leading 4 bytes as required by
// https://github.com/ethereum/execution-apis/blob/main/src/engine/identification.md
lystopad added 5 commits July 8, 2026 09:56
When a validator proposes a block without specifying graffiti, Caplin now
fills it with the client-version graffiti standard instead of the literal
"Caplin" string, so client-diversity tooling can attribute the block to its
execution and consensus clients:

  <EL code><EL commit><CN><CL commit>   e.g. EGa53eCNa53e

Caplin uses the reserved consensus client code CN. The execution client's
code and commit are obtained via engine_getClientVersionV1, which is added to
the ExecutionEngine interface; the result is cached so steady-state block
production stays off the engine API. When the execution client does not
support the method, the graffiti degrades to the consensus client identifier
only. User-specified graffiti is unaffected.

Standard: https://github.com/ethereum/execution-apis/blob/main/src/engine/identification.md
executionClientVersion only cached a successful engine_getClientVersionV1
response, so an execution client that does not implement the method (or returns
an empty list) was re-queried on every block proposal, incurring the engine API
round-trip and its 1s timeout each time. Memoize the unavailable outcome via a
sentinel so steady-state block production stays off the engine API in that case
too.
…version

Address review follow-ups on the default-graffiti client version lookup:

- executionClientVersion memoized the negative result on any engine error,
  so a transient failure (including the 1s context timeout) would disable EL
  attribution until restart. Only cache the unavailable outcome when the method
  is genuinely unsupported: a JSON-RPC method-not-found (-32601) error or an
  empty version list. Other errors are treated as transient and retried.
- NewClientVersionV1 now strips an existing "0x" prefix from the commit before
  re-prefixing, so a hex-prefixed input no longer yields "0x0x...".
On a cold cache, multiple concurrent block-production requests could each call
engine_getClientVersionV1 before the first result was stored, a small burst of
redundant engine calls at startup/reconnect. Collapse concurrent first-time
fetches through a singleflight.Group with a double-check inside the flight, so
only one engine call is in flight while the atomic-pointer cache and the
transient-vs-unsupported handling are unchanged.
… proposal path

Address review follow-ups on the default-graffiti client version lookup:

- Move the engine_getClientVersionV1 fetch off the block-production critical
  path. A cold cache no longer blocks the proposal for up to the 1s engine
  timeout (which risked missed slots when the EL is slow/unreachable); instead
  the first proposal falls back to consensus-only graffiti and a single
  background fetch populates the cache for later proposals. A one-shot atomic
  guard replaces the singleflight group and still ensures only one in-flight
  fetch, while transient errors stay uncached and are retried.
- Clamp the execution client code to the 2 bytes the graffiti standard reserves
  so a non-conforming EL code cannot misalign the encoding.
- Add the missing space in the GetClientVersionV1 request log message.
- Correct the NewClientVersionV1 doc comment to note the all-zero fallback for a
  missing or too-short commit hash.
@lystopad
lystopad force-pushed the feature/lystopad/caplin-graffiti-client-version branch from 0c1bffe to 9121d16 Compare July 8, 2026 08:20
@lystopad lystopad added this to the 3.6.0 milestone Jul 8, 2026
@lystopad
lystopad added this pull request to the merge queue Jul 8, 2026
Merged via the queue into main with commit aa2907d Jul 8, 2026
93 checks passed
@lystopad
lystopad deleted the feature/lystopad/caplin-graffiti-client-version branch July 8, 2026 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Caplin Caplin: Consensus Layer, Beacon API

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants