Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
eb37595
Add Gloas bid selection, block production, and bid gossip processing …
ethDreamer Aug 13, 2026
99bddcf
Convert produceBlockV4 to POST and round-trip Eth-Builder-Url (Gloas …
ethDreamer Aug 13, 2026
44f4424
Migrate the validator client to the Gloas builder API (Gloas builder …
ethDreamer Aug 13, 2026
6b1bcef
Fix stale and incorrectly versioned builder preference submissions
jimmygchen Aug 28, 2026
a03f186
Convert produceBlockV4 to POST and round-trip Eth-Builder-Url (Gloas …
ethDreamer Aug 13, 2026
2a6fc09
Migrate the validator client to the Gloas builder API (Gloas builder …
ethDreamer Aug 13, 2026
647cc64
Merge updated base to resolve PR #9929 conflicts
jimmygchen Sep 9, 2026
6d9d200
Use HTTP status for builder preference retries
jimmygchen Sep 9, 2026
fd10252
Simplify builder preference retries and test mocks
jimmygchen Sep 9, 2026
714ab7b
Simplify builder preference submission retries
jimmygchen Sep 9, 2026
81335ac
Convert produceBlockV4 to POST and round-trip Eth-Builder-Url (Gloas …
ethDreamer Aug 13, 2026
a434c7d
Migrate the validator client to the Gloas builder API (Gloas builder …
ethDreamer Aug 13, 2026
cd77cc3
Merge updated builder API base into preference submission fix
jimmygchen Sep 11, 2026
5bbe535
Merge unstable and reconcile builder preference submission fixes
jimmygchen Sep 18, 2026
272976b
Merge unstable and preserve builder preference config updates
jimmygchen Sep 18, 2026
cb9a7a1
Merge branch 'unstable' into codex/fix-builder-preference-submissions
jimmygchen Sep 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 24 additions & 16 deletions common/eth2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2234,47 +2234,55 @@ impl BeaconNodeHttpClient {
/// Ask the beacon node to submit builder preferences ahead of the bid request (beacon-APIs #630).
/// The body is a flat list of entries, each naming its own `proposer_pubkey`, so one request may
/// cover several proposers. `fork_name` is sent as the required `Eth-Consensus-Version` header.
/// Returns the HTTP response, including error responses, so callers can distinguish its status
/// from an application-specific error code in the body.
pub async fn post_validator_builder_preferences(
&self,
entries: &SubmittedBuilderPreferences,
fork_name: ForkName,
) -> Result<(), Error> {
) -> Result<Response, Error> {
let mut path = self.eth_path(V1)?;

path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("builder_preferences");

self.post_with_timeout_and_consensus_header(
path,
&entries,
self.timeouts.default,
fork_name,
)
.await?;

Ok(())
self.client
.post(path)
.timeout(self.timeouts.default)
.header(CONSENSUS_VERSION_HEADER, fork_name.to_string())
.json(entries)
.send()
.await
.map_err(Into::into)
}

/// `POST validator/builder_preferences` (SSZ)
///
/// Returns the HTTP response, including error responses, as in
/// [`Self::post_validator_builder_preferences`].
pub async fn post_validator_builder_preferences_ssz(
&self,
entries: &SubmittedBuilderPreferences,
fork_name: ForkName,
) -> Result<(), Error> {
) -> Result<Response, Error> {
let mut path = self.eth_path(V1)?;

path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("builder_preferences");

let ssz_body = entries.as_ssz_bytes();
self.post_generic_with_consensus_version_and_ssz_body(path, ssz_body, None, fork_name)
.await?;

Ok(())
self.client
.post(path)
.timeout(self.timeouts.default)
.header(CONSENSUS_VERSION_HEADER, fork_name.to_string())
.header(CONTENT_TYPE_HEADER, SSZ_CONTENT_TYPE_HEADER)
.body(entries.as_ssz_bytes())
.send()
.await
.map_err(Into::into)
}

/// `GET config/fork_schedule`
Expand Down
83 changes: 70 additions & 13 deletions testing/validator_test_rig/src/mock_beacon_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,23 +356,80 @@ impl<E: EthSpec> MockBeaconNode<E> {
.create()
}

/// Mocks `POST /eth/v1/validator/builder_preferences` (SSZ), matching only submissions whose
/// `Eth-Consensus-Version` header names `fork_name`.
pub fn mock_post_validator_builder_preferences_ssz(&mut self, fork_name: ForkName) -> Mock {
let received = Arc::clone(&self.builder_preferences);
/// Mocks `POST /eth/v1/validator/builder_preferences` (SSZ).
pub fn mock_post_validator_builder_preferences_ssz(
&mut self,
fork_name: ForkName,
status: u16,
response_body: &str,
) -> Mock {
self.mock_post_validator_builder_preferences_ssz_with_hook(
fork_name,
status,
response_body,
|| {},
)
}

/// Mocks `POST /eth/v1/validator/builder_preferences` (SSZ) and runs `hook` after receipt.
pub fn mock_post_validator_builder_preferences_ssz_with_hook<F>(
&mut self,
fork_name: ForkName,
status: u16,
response_body: &str,
hook: F,
) -> Mock
where
F: Fn() + Send + Sync + 'static,
{
let builder_preferences = Arc::clone(&self.builder_preferences);
let response_body = response_body.as_bytes().to_vec();

self.server
.mock(
"POST",
Matcher::Regex(r"^/eth/v1/validator/builder_preferences$".to_string()),
.mock("POST", "/eth/v1/validator/builder_preferences")
.match_header("content-type", "application/octet-stream")
.match_header(
CONSENSUS_VERSION_HEADER,
Matcher::Exact(fork_name.to_string()),
)
.match_header("Eth-Consensus-Version", fork_name.to_string().as_str())
.with_status(200)
.with_status(status as usize)
.with_header("content-type", "application/json")
.with_body_from_request(move |request| {
let body = request.body().expect("Failed to get request body");
let preferences = SubmittedBuilderPreferences::from_ssz_bytes(body)
.expect("Failed to deserialize SubmittedBuilderPreferences from SSZ");
received.lock().unwrap().push(preferences);
vec![]
let entries = SubmittedBuilderPreferences::from_ssz_bytes(body)
.expect("Failed to deserialize builder preferences as SSZ");
builder_preferences.lock().unwrap().push(entries);
hook();
response_body.clone()
})
.create()
}

/// Mocks `POST /eth/v1/validator/builder_preferences` (JSON).
pub fn mock_post_validator_builder_preferences_json(
&mut self,
fork_name: ForkName,
status: u16,
response_body: &str,
) -> Mock {
let builder_preferences = Arc::clone(&self.builder_preferences);
let response_body = response_body.as_bytes().to_vec();

self.server
.mock("POST", "/eth/v1/validator/builder_preferences")
.match_header("content-type", "application/json")
.match_header(
CONSENSUS_VERSION_HEADER,
Matcher::Exact(fork_name.to_string()),
)
.with_status(status as usize)
.with_header("content-type", "application/json")
.with_body_from_request(move |request| {
let body = request.body().expect("Failed to get request body");
let entries: SubmittedBuilderPreferences = serde_json::from_slice(body)
.expect("Failed to deserialize builder preferences as JSON");
builder_preferences.lock().unwrap().push(entries);
response_body.clone()
})
.create()
}
Expand Down
Loading
Loading