Skip to content

Rewrite submit_block relay communication to support SSZ and JSON#468

Open
JasonVranek wants to merge 1 commit into
mainfrom
pr/ssz-submit-block
Open

Rewrite submit_block relay communication to support SSZ and JSON#468
JasonVranek wants to merge 1 commit into
mainfrom
pr/ssz-submit-block

Conversation

@JasonVranek

Copy link
Copy Markdown
Collaborator

Summary

Rewrites submit_block relay communication to support SSZ and JSON content negotiation.

What's in this PR

  • SSZ-first request encoding with automatic JSON fallback on 406/415 (per builder-spec)
  • Content-Type and Eth-Consensus-Version header handling for submit_block
  • Fork-aware SSZ decoding for relay responses
  • v2→v1 fallback now forwards the v1 payload to the beacon node instead of returning an empty 202 (prevents silent block loss)
  • V2_FALLBACK_TO_V1 metric counter for observability
  • Submit_block integration tests covering SSZ/JSON encoding, v1/v2 paths, and fallback behavior

Review notes

The v2→v1 fallback behavior is an intentional change from the previous code which silently discarded the v1 payload. The beacon node needs the unblinded execution payload to broadcast the block (returning an empty 202 on fallback would cause silent block loss).

Part 3 of 4: SSZ types → get_header → submit_block → validation bypassing

Attribution

This work builds on the SSZ builder flow originally implemented by @eserilev
in #252 and ported to the current codebase by @jclapis in #403.

Co-authored-by: Eitan Seri-Levi eserilev@ucsc.edu
Co-authored-by: Joe Clapis jclapis@outlook.com

@JasonVranek JasonVranek force-pushed the pr/ssz-routes branch 2 times, most recently from c800921 to cbf451d Compare June 2, 2026 03:30
@JasonVranek JasonVranek force-pushed the pr/ssz-submit-block branch from d468365 to 3740824 Compare July 10, 2026 20:23
Base automatically changed from pr/ssz-routes to main July 10, 2026 22:20
@JasonVranek JasonVranek requested a review from a team July 10, 2026 22:20
content negotiation. Includes:
- SSZ-first request encoding with JSON fallback on 406/415
- Content-Type and Eth-Consensus-Version header handling
- Fork-aware SSZ decoding for relay responses
- MIME parameter tolerance on relay response Content-Type
- v2 to v1 fallback forwards payload to BN (prevents silent block loss)
- V2 fallback metric counter
- Comprehensive submit_block integration tests for both encodings
@JasonVranek JasonVranek force-pushed the pr/ssz-submit-block branch from 3740824 to 0d76e5c Compare July 10, 2026 22:29
Ok(response) => {
// If the original request was for v2 but we had to fall back to v1, return a v2
// response
// If the original request was for v2 but we had to fall back to v1, the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Would be great if these fallbacks could be removed. The v1 endpoint has been deprecated for a long time now. But relays still support it because boost clients still call it and boost clients still call it because it's available as a fallback.

I'd be surprised if there are still relevant relays that don't yet support v2 at this point.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

agreed it would be nice to prune the fallbacks but in the off chance the edge case is hit, this is purely more redundancy with no latency penalty. (I believe during kurtosis testing we hit this all the time too hence why it was added). I'd argue keep for now and after gloas v1/v2 are deprecated anyways

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yeah fair point about Gloas. Hardly worth trying to remove it now at this stage when gloas is not too far away.

.post(url.as_ref().clone())
.timeout(Duration::from_millis(timeout_ms))
.headers(headers)
.body(serde_json::to_vec(&signed_blinded_block).unwrap())

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.

unwrap -> map_err into PbsError ?

res = match relay
.client
.post(url.as_ref().clone())
.timeout(Duration::from_millis(timeout_ms))

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.

because this doesn't run concurrently with the previous call, this function can take timeout_ms x2 and then the submit_block_with_timeout's accounting of remaining_timeout_ms is incorrect:

remaining_timeout_ms = timeout_ms.saturating_sub(start_request.elapsed().as_millis() as u64);

) -> Result<SubmitBlindedBlockResponse, PbsError> {
let data = PayloadAndBlobs::from_ssz_bytes_by_fork(response_bytes, fork).map_err(|e| {
PbsError::RelayResponse {
error_msg: (format!("error decoding relay payload: {e:?}")).to_string(),

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.

don't need .to_string()

.timeout(Duration::from_millis(timeout_ms))
.headers(headers)
.body(serde_json::to_vec(&signed_blinded_block).unwrap())
.header(CONTENT_TYPE, EncodingType::Json.to_string())

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.

EncodingType::Json.content_type_header()

.json(&signed_blinded_block)
.headers(headers.clone())
.body(signed_blinded_block.as_ssz_bytes())
.header(CONTENT_TYPE, EncodingType::Ssz.to_string())

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.

EncodingType::Ssz.content_type_header()

relay,
url,
timeout_ms,
proposal_info.headers.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.

let's pass by reference, we need to clone (not move) inside once anyway

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.

3 participants