Skip to content

Convert a submitted payload to an ethrex block and locate its parent - #531

Open
0w3n-d wants to merge 1 commit into
od/sim-shared-payment-helpers-step2from
od/builder-sim-validation-step3
Open

Convert a submitted payload to an ethrex block and locate its parent#531
0w3n-d wants to merge 1 commit into
od/sim-shared-payment-helpers-step2from
od/builder-sim-validation-step3

Conversation

@0w3n-d

@0w3n-d 0w3n-d commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Issue: #527 (step 3 of 10)

Base branch: od/sim-shared-payment-helpers-step2 (#529, step 2). Retarget as
the stack merges.

What this PR does

Adds BlockValidator. It turns an ExecutionPayloadV3 into the ethrex block
the submission describes, checks the bid trace against that block, and finds the
parent inside the validation window. payload_v3_to_block is the inverse of the
existing block_to_payload_v3.

Two departures from the reth simulator, both deliberate:

  • The head comes from the node's existing watch channel, not a store read per
    call, so prepare stays synchronous. A lagging head can only shorten the
    measured distance, so it errs toward accepting, never rejecting.
  • registered_gas_limit is not threaded in. reth's _validate_gas_limit is
    dead code there, so there is nothing to port.

Also extracts the merge-engine test fixture's shared primitives into
src/testing.rs. Every later validation step needs them, and the merge-specific
Fixture stays where it is.

What this PR deliberately does not do

Nothing executes. No EVM, no state root, no payment, no blacklist. main does
not reach BlockValidator yet either, which is why the module carries an
allow(dead_code) until step 9 wires the servers.

Checks ethrex already performs are not reported here: the header against the
parent, the RLP block size and the per-tx chain id all live in
validate_block_pre_execution, which step 4 calls.

Tests

Written first and signed off before implementation
(src/validation/tests.rs, 12 tests on an in-memory devnet chain):

  • A fixture-built block survives the payload round trip with the same hash, and
    an undecodable transaction is rejected.
  • A bid trace with the wrong block hash, parent hash, gas limit or gas used is
    each rejected, in that order. A payload edited after signing fails on the
    block hash rather than a per-field check.
  • The head is accepted as a parent; an unknown parent and one outside the window
    are rejected; one inside it passes.
  • BlockSimError::BlockValidationFailed(err.to_string()) still reaches
    is_temporary() and is_too_old(). This pins the two error strings the relay
    matches on. Get them wrong and a transient failure demotes the builder.

just fmt-check, just test and cargo clippy --all-features --no-deps -- -D warnings
are clean. 46 tests pass in helix-builder, up from 34.

Reviewer checklist

  • CI (lint, unit-test) is green
  • Matches the linked issue/step
  • No unexplained scope creep or unrelated files touched

BlockValidator turns an ExecutionPayloadV3 into the block it describes,
checks the bid trace against that block, and finds the parent within the
validation window. Nothing executes yet.

The head comes from the node's watch channel rather than a store read per
call, which keeps prepare synchronous.

Step 3 of #527.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@0w3n-d
0w3n-d force-pushed the od/builder-sim-validation-step3 branch from e8cb893 to f252ec9 Compare August 29, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants