Skip to content

Withhold V1 getPayload response inside a safety buffer before the cutoff - #520

Merged
0w3n-d merged 1 commit into
developfrom
od/get-payload-v1-response-buffer
Aug 26, 2026
Merged

Withhold V1 getPayload response inside a safety buffer before the cutoff#520
0w3n-d merged 1 commit into
developfrom
od/get-payload-v1-response-buffer

Conversation

@0w3n-d

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

Copy link
Copy Markdown
Collaborator

Issue: #519 (step 1 of 1)

What this PR does

A V1 getPayload request accepted right up against the slot cutoff still gets the unblinded payload back over the API, even though the relay's own publish is likely too late to be attested to. A proposer holding two slots in a row could exploit that deliberately: let the relay-published block orphan, then use the leaked payload to unbundle the MEV into their own next-slot block.

Adds a configurable response-safety buffer (get_payload_v1_response_buffer_ms, default 800ms) before the existing cutoff. A V1 request landing in that window is still built and published to the beacon client exactly as before; the API response withholds the payload and returns the same GetPayloadRequestTooLate error an outright-late request already gets, so it's indistinguishable from ordinary lateness. A new get_payload_v1_response_withheld_total counter makes this observable.

What this PR deliberately does not do

  • Does not change V2 behavior — V2 never returns the payload over the API, so it isn't affected by this vector.
  • Does not add handler-level (_get_payload) integration tests exercising the beacon-publish/DB side effects — there's no existing mock/test-double infrastructure for ProposerApi's dependencies (DbHandle, MultiBeaconClient, GrpcGossiperClientManager, AuctioneerHandle, the Api/ApiProvider traits) anywhere in the codebase, and building that is its own scope of work (noted as a possible follow-up in the issue).
  • Does not drop V1 support outright (the other option considered) — this keeps it working for honest callers.

Tests

Written before implementation, reviewed and approved before writing the behavior change. The cutoff/buffer decision is extracted into a small pure function (evaluate_response_buffer, mirroring the existing stream_window pattern in header_stream.rs) with unit tests covering: safely inside the window, just inside the buffer, exactly at the outer cutoff boundary, past the cutoff, and a zero-buffer no-op.

Reviewer checklist

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

A V1 getPayload request accepted right up against the slot cutoff still
gets the unblinded payload back over the API, even though the relay's own
publish is likely too late to be attested to. A proposer holding two slots
in a row could exploit that deliberately: let the relay-published block
orphan, then use the leaked payload to unbundle the MEV into their own
next-slot block.

Add a configurable response-safety buffer (default 800ms) before the
cutoff. Requests inside it are still built and published to the beacon
client as before, but the API withholds the payload and returns the same
too-late error an outright-late request already gets.

Issue: #519 (step 1)
@0w3n-d
0w3n-d merged commit d4a27c5 into develop Aug 26, 2026
2 checks passed
@0w3n-d
0w3n-d deleted the od/get-payload-v1-response-buffer branch August 26, 2026 13:28
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