Skip to content

feat(actions): Wire Production Payload Builder#1861

Open
refcell wants to merge 7 commits intomainfrom
feat/actions-wire-production-payload-builder
Open

feat(actions): Wire Production Payload Builder#1861
refcell wants to merge 7 commits intomainfrom
feat/actions-wire-production-payload-builder

Conversation

@refcell
Copy link
Copy Markdown
Contributor

@refcell refcell commented Apr 1, 2026

Summary

Replaces the bespoke StatefulL2Executor inside ActionEngineClient with the real OpPayloadBuilder stack. L2Sequencer now uses the production L1OriginSelector and StatefulAttributesBuilder to produce payload attributes, then delegates block building to ActionEngineClient via start_build_block / get_sealed_payload / insert_unsafe_payload. This eliminates the divergence between the test harness and the production block-building code path, so action tests exercise the real execution pipeline end-to-end. get_next_payload_l1_origin and build_attributes on PayloadBuilder are made pub to support this wiring.

@refcell refcell added K-feature Kind: New feature M-refactor Meta: refactors code A-actions Area: actions testing labels Apr 1, 2026
@refcell refcell self-assigned this Apr 1, 2026
@refcell refcell added K-feature Kind: New feature M-refactor Meta: refactors code A-actions Area: actions testing labels Apr 1, 2026
@refcell refcell requested review from danyalprout and jackchuma April 1, 2026 18:33
@cb-heimdall
Copy link
Copy Markdown
Collaborator

cb-heimdall commented Apr 1, 2026

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
base Ignored Ignored Preview Apr 8, 2026 9:43pm

Request Review

@refcell refcell marked this pull request as draft April 1, 2026 18:35
@github-actions github-actions bot added the M-prevent-stale Meta: Not Stale label Apr 2, 2026
@refcell refcell force-pushed the feat/actions-wire-production-payload-builder branch from cb494fe to 4b29e90 Compare April 3, 2026 12:20
@refcell refcell force-pushed the feat/actions-wire-production-payload-builder branch from 2408906 to a4b6082 Compare April 5, 2026 16:27
@refcell refcell marked this pull request as ready for review April 6, 2026 16:43
@refcell refcell force-pushed the feat/actions-wire-production-payload-builder branch from d689e5d to a26a7f2 Compare April 8, 2026 21:43
let block_hash = block.hash();
let parent_beacon_block_root = block.header().parent_beacon_block_root();
let (payload, _sidecar) =
OpExecutionPayload::from_block_unchecked(block_hash, &block.clone_block());
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.

The sidecar returned by from_block_unchecked is discarded here, but the caller (L2Sequencer::try_build_next_block_with_transactions) then manually reconstructs a sidecar from the envelope to restore PBBR and requests_hash (l2.rs:546-560). This round-trip is fragile: if from_block_unchecked ever changes what it puts in the sidecar vs the payload, the reconstruction logic in L2Sequencer could silently diverge.

Consider returning the sidecar as part of OpExecutionPayloadEnvelope (or a richer return type), or at minimum passing the built payload's sidecar through so the caller doesn't have to reconstruct it from scratch.


// 5. Insert the block into the engine (updates canonical head).
self.engine_client
.insert_unsafe_payload(envelope.clone())
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.

envelope.clone() deep-clones the entire OpExecutionPayloadEnvelope (including all transactions) only to immediately pass the original to try_into_block_with_sidecar on line 561. Since insert_unsafe_payload takes ownership, consider reordering: extract the fields needed for the BaseBlock conversion first, then pass the original envelope to insert_unsafe_payload.

Alternatively, since insert_unsafe_payload on the in-process engine client is a no-op (the skip-check fires because the block was just built by start_build_block), the clone is paying for work that doesn't do anything.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Review Summary

This PR replaces the bespoke StatefulL2Executor with the real OpPayloadBuilder stack, making the action test harness exercise the production block-building code path end-to-end. The architectural direction is sound — eliminating test/production divergence is the right move.

Key observations

Correctness: The core block-building and state-commitment flow in build_and_commit is correct. The PR properly stores full headers from the builder (fixing the prior partial-header issue), and the dedup/skip logic in execute_v1_inner handles both sequencer mode (real hash match) and derivation mode (B256::ZERO placeholder).

New findings (this review):

  1. Sidecar discarded in get_sealed_payload, reconstructed by caller (engine.rs:936): from_block_unchecked returns a sidecar that is discarded with _sidecar, forcing L2Sequencer to manually reconstruct it (l2.rs:546-560). This round-trip is fragile and could diverge if the builder's sidecar format changes.

  2. Unnecessary envelope.clone() in sequencer insert path (l2.rs:529): The envelope is cloned before insert_unsafe_payload, but insert_unsafe_payload is effectively a no-op on the in-process engine (the skip-check fires because the block was just built). The clone copies all transaction data for no effect.

Previously raised findings still applicable (from prior review run — not re-posted as inline comments):

  • use imports inside function bodies violate project convention (engine.rs:256,273; l2.rs:479)
  • run_async lacks panic-with-fork-name annotation unlike its sync sibling run (matrix.rs:122)
  • OpPayloadBuilder reconstructed on every build_and_commit call rather than stored once (engine.rs:335)
  • executed_block() returning None silently skips DB commit (engine.rs:349)
  • pending_payloads entries never removed after retrieval (engine.rs:540)
  • canonical_head.l1_origin is always zeroed (engine.rs:965)
  • execute_v1_inner constructs attrs with eip_1559_params: None, will fail for Holocene+ blocks that aren't pre-built (engine.rs:447)
  • insert_unsafe_payload down-converts to V1, dropping fork-specific parameters (engine.rs:949)
  • Genesis fallback in build_and_commit should validate parent hash more strictly (engine.rs:311)
  • pub on get_next_payload_l1_origin and build_attributes has no current consumer (build.rs:110)
  • L2 provider system config is never updated for new epochs (l2.rs:591)

Verdict

The PR achieves its stated goal of wiring the production payload builder into the action test harness. The architecture is an improvement. The findings above are mostly hardening suggestions and style issues — none are blocking for the current test-only scope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-actions Area: actions testing K-feature Kind: New feature M-prevent-stale Meta: Not Stale M-refactor Meta: refactors code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants