@@ -15,8 +15,7 @@ use cb_common::{
1515 utils:: utcnow_ms,
1616 wire:: {
1717 AcceptedEncodings , CONSENSUS_VERSION_HEADER , EncodingType , build_outbound_accept,
18- get_accept_types, get_user_agent_with_version, parse_response_encoding_and_fork,
19- read_chunked_body_with_max,
18+ get_user_agent_with_version, parse_response_encoding_and_fork, read_chunked_body_with_max,
2019 } ,
2120} ;
2221use futures:: { FutureExt , future:: select_ok} ;
@@ -25,13 +24,13 @@ use reqwest::{
2524 header:: { ACCEPT , CONTENT_TYPE , USER_AGENT } ,
2625} ;
2726use ssz:: Encode ;
28- use tracing:: { debug, error , warn} ;
27+ use tracing:: { debug, warn} ;
2928use url:: Url ;
3029
3130use crate :: {
3231 TIMEOUT_ERROR_CODE_STR ,
3332 constants:: { MAX_SIZE_SUBMIT_BLOCK_RESPONSE , SUBMIT_BLINDED_BLOCK_ENDPOINT_TAG } ,
34- metrics:: { RELAY_LATENCY , RELAY_STATUS_CODE , V2_FALLBACK_TO_V1 } ,
33+ metrics:: { RELAY_LATENCY , RELAY_STATUS_CODE , RELAY_V2_UNSUPPORTED } ,
3534 state:: { BuilderApiState , PbsState } ,
3635} ;
3736
@@ -55,8 +54,8 @@ struct SubmitBlockResponseInfo {
5554 /// ACCEPTED/OK paths where no body is returned.
5655 content_type : Option < EncodingType > ,
5756
58- /// Which fork the response bid is for (if provided as a header, rather than
59- /// part of the body)
57+ /// Which fork the response payload is for (if provided as a header, rather
58+ /// than part of the body)
6059 fork : Option < ForkName > ,
6160
6261 /// The status code of the response, for logging
@@ -82,21 +81,21 @@ pub async fn submit_block<S: BuilderApiState>(
8281 send_headers. insert ( HEADER_START_TIME_UNIX_MS , HeaderValue :: from ( utcnow_ms ( ) ) ) ;
8382 send_headers. insert ( USER_AGENT , get_user_agent_with_version ( & req_headers) ?) ;
8483
85- // Forward the caller's Accept preference to the relay so the relay
86- // returns data in the format the BN expects, avoiding decode→re-encode.
87- // Always offer both encodings as fallback so a format-limited relay
88- // still returns a bid (PBS converts if needed).
89- let caller_accept = get_accept_types ( & req_headers ) . inspect_err ( |err| {
90- error ! ( %err , "error parsing accept header" ) ;
91- } ) ? ;
92- let relay_accept = AcceptedEncodings {
93- primary : caller_accept . primary ,
94- fallback : Some ( match caller_accept . primary {
95- EncodingType :: Ssz => EncodingType :: Json ,
96- EncodingType :: Json => EncodingType :: Ssz ,
97- } ) ,
98- } ;
99- send_headers . insert ( ACCEPT , build_outbound_accept ( relay_accept ) ) ;
84+ // PBS always decodes and re-validates the relay payload, then the route
85+ // re-encodes it to the BN's Accept. So always request SSZ from the relay
86+ // (smaller on the wire, faster to decode than JSON); JSON is the fallback for
87+ // a relay that can't do SSZ. The BN's own format preference is applied later
88+ // by the route, not here. Skip for v2, whose success is an empty 202 with no
89+ // body to negotiate.
90+ if api_version == BuilderApiVersion :: V1 {
91+ send_headers . insert (
92+ ACCEPT ,
93+ build_outbound_accept ( AcceptedEncodings {
94+ primary : EncodingType :: Ssz ,
95+ fallback : Some ( EncodingType :: Json ) ,
96+ } ) ,
97+ ) ;
98+ }
10099
101100 // Send requests to all relays concurrently
102101 let proposal_info =
@@ -130,11 +129,11 @@ async fn submit_block_with_timeout(
130129 relay : RelayClient ,
131130 timeout_ms : u64 ,
132131) -> Result < Option < SubmitBlindedBlockResponse > , PbsError > {
133- let mut url = Arc :: new ( relay. submit_block_url ( proposal_info. api_version ) ?) ;
132+ let url = Arc :: new ( relay. submit_block_url ( proposal_info. api_version ) ?) ;
134133 let mut remaining_timeout_ms = timeout_ms;
135134 let mut retry = 0 ;
136135 let mut backoff = Duration :: from_millis ( 250 ) ;
137- let mut request_api_version = proposal_info. api_version ;
136+ let request_api_version = proposal_info. api_version ;
138137
139138 loop {
140139 let start_request = Instant :: now ( ) ;
@@ -148,23 +147,7 @@ async fn submit_block_with_timeout(
148147 )
149148 . await
150149 {
151- Ok ( response) => {
152- // If the original request was for v2 but we had to fall back to v1, the
153- // V1 response body (execution payload + blobs bundle) MUST be forwarded
154- // back to the beacon node so the proposer can broadcast. Returning an
155- // empty 202 here would cause silent block loss because the BN never
156- // receives the unblinded payload.
157- if request_api_version == BuilderApiVersion :: V1 &&
158- proposal_info. api_version != request_api_version
159- {
160- warn ! (
161- relay_id = relay. id. as_ref( ) ,
162- "v2 submit_block fell back to v1; forwarding v1 payload to beacon node"
163- ) ;
164- V2_FALLBACK_TO_V1 . with_label_values ( & [ relay. id . as_ref ( ) ] ) . inc ( ) ;
165- }
166- return Ok ( response) ;
167- }
150+ Ok ( response) => return Ok ( response) ,
168151
169152 Err ( err) if err. should_retry ( ) => {
170153 tokio:: time:: sleep ( backoff) . await ;
@@ -178,15 +161,18 @@ async fn submit_block_with_timeout(
178161 }
179162 }
180163
181- Err ( err)
182- if err. is_not_found ( ) && matches ! ( request_api_version, BuilderApiVersion :: V2 ) =>
183- {
164+ // A relay that 404s the v2 endpoint cannot serve a v2 submission. In
165+ // v2 the relay itself publishes the block after an empty 202, so a v1
166+ // payload is useless here: the beacon node expects 202 and will not
167+ // read a 200 body, so forwarding it would silently drop the block.
168+ // Fail loud instead, and let another relay (if any) serve v2.
169+ Err ( err) if err. is_not_found ( ) && request_api_version == BuilderApiVersion :: V2 => {
184170 warn ! (
185171 relay_id = relay. id. as_ref( ) ,
186- "relay does not support v2 endpoint, retrying with v1 "
172+ "relay does not support the v2 submit_block endpoint; cannot serve this submission "
187173 ) ;
188- url = Arc :: new ( relay. submit_block_url ( BuilderApiVersion :: V1 ) ? ) ;
189- request_api_version = BuilderApiVersion :: V1 ;
174+ RELAY_V2_UNSUPPORTED . with_label_values ( & [ relay. id . as_ref ( ) ] ) . inc ( ) ;
175+ return Err ( err ) ;
190176 }
191177
192178 Err ( err) => return Err ( err) ,
0 commit comments