From 661ee56e49383d5ef02475541a334a0d099dd6ec Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Tue, 22 Jul 2025 18:46:01 -0700 Subject: [PATCH 1/8] Initial commit --- beacon_node/builder_client/src/lib.rs | 130 ++++++++++++++++++++++++- beacon_node/execution_layer/src/lib.rs | 13 ++- 2 files changed, 138 insertions(+), 5 deletions(-) diff --git a/beacon_node/builder_client/src/lib.rs b/beacon_node/builder_client/src/lib.rs index d193eaf1d80..1cbff4aff16 100644 --- a/beacon_node/builder_client/src/lib.rs +++ b/beacon_node/builder_client/src/lib.rs @@ -292,10 +292,25 @@ impl BuilderHttpClient { Ok(()) } - /// `POST /eth/v1/builder/blinded_blocks` with SSZ serialized request body pub async fn post_builder_blinded_blocks_ssz( &self, blinded_block: &SignedBlindedBeaconBlock, + ) -> Result>, Error> { + if blinded_block.fork_name_unchecked().fulu_enabled() { + self.post_builder_blinded_blocks_v2_ssz(blinded_block) + .await + .map(|_| None) + } else { + self.post_builder_blinded_blocks_v1_ssz(blinded_block) + .await + .map(Some) + } + } + + /// `POST /eth/v1/builder/blinded_blocks` with SSZ serialized request body + pub async fn post_builder_blinded_blocks_v1_ssz( + &self, + blinded_block: &SignedBlindedBeaconBlock, ) -> Result, Error> { let mut path = self.server.full.clone(); @@ -340,10 +355,73 @@ impl BuilderHttpClient { .map_err(Error::InvalidSsz) } - /// `POST /eth/v1/builder/blinded_blocks` + /// `POST /eth/v2/builder/blinded_blocks` with SSZ serialized request body + pub async fn post_builder_blinded_blocks_v2_ssz( + &self, + blinded_block: &SignedBlindedBeaconBlock, + ) -> Result<(), Error> { + let mut path = self.server.full.clone(); + + let body = blinded_block.as_ssz_bytes(); + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("eth") + .push("v2") + .push("builder") + .push("blinded_blocks"); + + let mut headers = HeaderMap::new(); + headers.insert( + CONSENSUS_VERSION_HEADER, + HeaderValue::from_str(&blinded_block.fork_name_unchecked().to_string()) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + headers.insert( + CONTENT_TYPE_HEADER, + HeaderValue::from_str(SSZ_CONTENT_TYPE_HEADER) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + headers.insert( + ACCEPT, + HeaderValue::from_str(PREFERENCE_ACCEPT_VALUE) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + + let result = self + .post_ssz_with_raw_response( + path, + body, + headers, + Some(self.timeouts.post_blinded_blocks), + ) + .await?; + + if result.status() == StatusCode::ACCEPTED { + Ok(()) + } else { + // ACCEPTED is the only valid status code response + Err(Error::StatusCode(result.status())) + } + } + pub async fn post_builder_blinded_blocks( &self, blinded_block: &SignedBlindedBeaconBlock, + ) -> Result>>, Error> { + if blinded_block.fork_name_unchecked().fulu_enabled() { + self.post_builder_blinded_blocks_v2(blinded_block) + .await + .map(|_| None) + } else { + self.post_builder_blinded_blocks_v1(blinded_block).await.map(Some) + } + } + + /// `POST /eth/v1/builder/blinded_blocks` + pub async fn post_builder_blinded_blocks_v1( + &self, + blinded_block: &SignedBlindedBeaconBlock, ) -> Result>, Error> { let mut path = self.server.full.clone(); @@ -383,6 +461,54 @@ impl BuilderHttpClient { .await?) } + /// `POST /eth/v2/builder/blinded_blocks` + pub async fn post_builder_blinded_blocks_v2( + &self, + blinded_block: &SignedBlindedBeaconBlock, + ) -> Result<(), Error> { + let mut path = self.server.full.clone(); + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("eth") + .push("v2") + .push("builder") + .push("blinded_blocks"); + + let mut headers = HeaderMap::new(); + headers.insert( + CONSENSUS_VERSION_HEADER, + HeaderValue::from_str(&blinded_block.fork_name_unchecked().to_string()) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + headers.insert( + CONTENT_TYPE_HEADER, + HeaderValue::from_str(JSON_CONTENT_TYPE_HEADER) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + headers.insert( + ACCEPT, + HeaderValue::from_str(JSON_ACCEPT_VALUE) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + + let result = self + .post_with_raw_response( + path, + &blinded_block, + headers, + Some(self.timeouts.post_blinded_blocks), + ) + .await?; + + if result.status() == StatusCode::ACCEPTED { + Ok(()) + } else { + // ACCEPTED is the only valid status code response + Err(Error::StatusCode(result.status())) + } + } + /// `GET /eth/v1/builder/header` pub async fn get_builder_header( &self, diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index cf751138d63..fc1155588b4 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -1893,7 +1893,7 @@ impl ExecutionLayer { &self, block_root: Hash256, block: &SignedBlindedBeaconBlock, - ) -> Result, Error> { + ) -> Result>, Error> { debug!(?block_root, "Sending block to builder"); if let Some(builder) = self.builder() { @@ -1915,13 +1915,13 @@ impl ExecutionLayer { .post_builder_blinded_blocks(block) .await .map_err(Error::Builder) - .map(|d| d.data) + .map(|d| d.map(|resp| resp.data)) } }) .await; match &payload_result { - Ok(unblinded_response) => { + Ok(Some(unblinded_response)) => { metrics::inc_counter_vec( &metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME, &[metrics::SUCCESS], @@ -1936,6 +1936,13 @@ impl ExecutionLayer { "Builder successfully revealed payload" ) } + Ok(None) => { + info!( + relay_response_ms = duration.as_millis(), + ?block_root, + "Builder returned a successful response" + ); + } Err(e) => { metrics::inc_counter_vec( &metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME, From ced43c3808d98d36ac2fb5251ba6fef5fe208735 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Wed, 23 Jul 2025 11:46:29 +1000 Subject: [PATCH 2/8] Serve ConfigAndPreset for latest_stable fork --- beacon_node/http_api/tests/tests.rs | 18 ++++++++++++------ consensus/types/src/config_and_preset.rs | 8 +++++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index ecd20f3f79c..f6dc435e607 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -2616,12 +2616,18 @@ impl ApiTester { } pub async fn test_get_config_spec(self) -> Self { - let result = self - .client - .get_config_spec::() - .await - .map(|res| ConfigAndPreset::Fulu(res.data)) - .unwrap(); + let result = if self.chain.spec.fulu_fork_epoch.is_some() { + self.client + .get_config_spec::() + .await + .map(|res| ConfigAndPreset::Fulu(res.data)) + } else { + self.client + .get_config_spec::() + .await + .map(|res| ConfigAndPreset::Electra(res.data)) + } + .unwrap(); let expected = ConfigAndPreset::from_chain_spec::(&self.chain.spec, None); assert_eq!(result, expected); diff --git a/consensus/types/src/config_and_preset.rs b/consensus/types/src/config_and_preset.rs index 235bf202382..728fa2d8dd6 100644 --- a/consensus/types/src/config_and_preset.rs +++ b/consensus/types/src/config_and_preset.rs @@ -43,7 +43,6 @@ pub struct ConfigAndPreset { } impl ConfigAndPreset { - // DEPRECATED: the `fork_name` argument is never used, we should remove it. pub fn from_chain_spec(spec: &ChainSpec, fork_name: Option) -> Self { let config = Config::from_chain_spec::(spec); let base_preset = BasePreset::from_chain_spec::(spec); @@ -53,8 +52,11 @@ impl ConfigAndPreset { let deneb_preset = DenebPreset::from_chain_spec::(spec); let extra_fields = get_extra_fields(spec); + // The latest stable fork is used if the `fork_name` is not specified. + let latest_stable_fork = ForkName::latest_stable(); + if spec.fulu_fork_epoch.is_some() - || fork_name.is_none() + || latest_stable_fork == ForkName::Fulu || fork_name == Some(ForkName::Fulu) { let electra_preset = ElectraPreset::from_chain_spec::(spec); @@ -72,7 +74,7 @@ impl ConfigAndPreset { extra_fields, }) } else if spec.electra_fork_epoch.is_some() - || fork_name.is_none() + || latest_stable_fork == ForkName::Electra || fork_name == Some(ForkName::Electra) { let electra_preset = ElectraPreset::from_chain_spec::(spec); From 097f7c6c1c72aad4ce7286eb53b24c1d65bca4b3 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Wed, 23 Jul 2025 12:54:08 +1000 Subject: [PATCH 3/8] Fix extra fields roundtrip test --- consensus/types/src/config_and_preset.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/consensus/types/src/config_and_preset.rs b/consensus/types/src/config_and_preset.rs index 728fa2d8dd6..84339b11b61 100644 --- a/consensus/types/src/config_and_preset.rs +++ b/consensus/types/src/config_and_preset.rs @@ -173,8 +173,8 @@ mod test { .write(false) .open(tmp_file.as_ref()) .expect("error while opening the file"); - let from: ConfigAndPresetFulu = + let from: ConfigAndPresetElectra = serde_yaml::from_reader(reader).expect("error while deserializing"); - assert_eq!(ConfigAndPreset::Fulu(from), yamlconfig); + assert_eq!(ConfigAndPreset::Electra(from), yamlconfig); } } From 696a7a76cd5c379462f963d200c44a7a8812ab19 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Wed, 23 Jul 2025 13:12:59 +1000 Subject: [PATCH 4/8] Fix VC test --- validator_client/http_api/src/tests.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/validator_client/http_api/src/tests.rs b/validator_client/http_api/src/tests.rs index 7d421cd7d58..d1e682568ba 100644 --- a/validator_client/http_api/src/tests.rs +++ b/validator_client/http_api/src/tests.rs @@ -206,13 +206,20 @@ impl ApiTester { } pub async fn test_get_lighthouse_spec(self) -> Self { - let result = self - .client - .get_lighthouse_spec::() - .await - .map(|res| ConfigAndPreset::Fulu(res.data)) - .unwrap(); - let expected = ConfigAndPreset::from_chain_spec::(&E::default_spec(), None); + let spec = E::default_spec(); + let result = if spec.fulu_fork_epoch.is_some() { + self.client + .get_lighthouse_spec::() + .await + .map(|res| ConfigAndPreset::Fulu(res.data)) + } else { + self.client + .get_lighthouse_spec::() + .await + .map(|res| ConfigAndPreset::Electra(res.data)) + } + .unwrap(); + let expected = ConfigAndPreset::from_chain_spec::(&spec, None); assert_eq!(result, expected); From 053d4504eafa4533935339560b8eccd1623dfd3f Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 23 Jul 2025 15:11:03 +1000 Subject: [PATCH 5/8] Complete Fulu builder API changes. --- beacon_node/builder_client/src/lib.rs | 28 ------ beacon_node/execution_layer/src/lib.rs | 100 ++++++++++++++++++--- beacon_node/http_api/src/publish_blocks.rs | 54 +++++++---- 3 files changed, 123 insertions(+), 59 deletions(-) diff --git a/beacon_node/builder_client/src/lib.rs b/beacon_node/builder_client/src/lib.rs index 1cbff4aff16..c1066042854 100644 --- a/beacon_node/builder_client/src/lib.rs +++ b/beacon_node/builder_client/src/lib.rs @@ -292,21 +292,6 @@ impl BuilderHttpClient { Ok(()) } - pub async fn post_builder_blinded_blocks_ssz( - &self, - blinded_block: &SignedBlindedBeaconBlock, - ) -> Result>, Error> { - if blinded_block.fork_name_unchecked().fulu_enabled() { - self.post_builder_blinded_blocks_v2_ssz(blinded_block) - .await - .map(|_| None) - } else { - self.post_builder_blinded_blocks_v1_ssz(blinded_block) - .await - .map(Some) - } - } - /// `POST /eth/v1/builder/blinded_blocks` with SSZ serialized request body pub async fn post_builder_blinded_blocks_v1_ssz( &self, @@ -405,19 +390,6 @@ impl BuilderHttpClient { } } - pub async fn post_builder_blinded_blocks( - &self, - blinded_block: &SignedBlindedBeaconBlock, - ) -> Result>>, Error> { - if blinded_block.fork_name_unchecked().fulu_enabled() { - self.post_builder_blinded_blocks_v2(blinded_block) - .await - .map(|_| None) - } else { - self.post_builder_blinded_blocks_v1(blinded_block).await.map(Some) - } - } - /// `POST /eth/v1/builder/blinded_blocks` pub async fn post_builder_blinded_blocks_v1( &self, diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index fc1155588b4..bfde2be17b0 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -411,6 +411,11 @@ pub enum FailedCondition { EpochsSinceFinalization, } +pub enum SubmitBlindedBlockResponse { + V1(Box>), + V2, +} + type PayloadContentsRefTuple<'a, E> = (ExecutionPayloadRef<'a, E>, Option<&'a BlobsBundle>); struct Inner { @@ -1893,9 +1898,25 @@ impl ExecutionLayer { &self, block_root: Hash256, block: &SignedBlindedBeaconBlock, - ) -> Result>, Error> { + spec: &ChainSpec, + ) -> Result, Error> { debug!(?block_root, "Sending block to builder"); + if spec.is_fulu_scheduled() { + self.post_builder_blinded_blocks_v2(block_root, block) + .await + .map(|()| SubmitBlindedBlockResponse::V2) + } else { + self.post_builder_blinded_blocks_v1(block_root, block) + .await + .map(|full_payload| SubmitBlindedBlockResponse::V1(Box::new(full_payload))) + } + } + async fn post_builder_blinded_blocks_v1( + &self, + block_root: Hash256, + block: &SignedBlindedBeaconBlock, + ) -> Result, Error> { if let Some(builder) = self.builder() { let (payload_result, duration) = timed_future(metrics::POST_BLINDED_PAYLOAD_BUILDER, async { @@ -1903,25 +1924,25 @@ impl ExecutionLayer { debug!( ?block_root, ssz = ssz_enabled, - "Calling submit_blinded_block on builder" + "Calling submit_blinded_block v1 on builder" ); if ssz_enabled { builder - .post_builder_blinded_blocks_ssz(block) + .post_builder_blinded_blocks_v1_ssz(block) .await .map_err(Error::Builder) } else { builder - .post_builder_blinded_blocks(block) + .post_builder_blinded_blocks_v1(block) .await .map_err(Error::Builder) - .map(|d| d.map(|resp| resp.data)) + .map(|d| d.data) } }) .await; match &payload_result { - Ok(Some(unblinded_response)) => { + Ok(unblinded_response) => { metrics::inc_counter_vec( &metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME, &[metrics::SUCCESS], @@ -1936,13 +1957,6 @@ impl ExecutionLayer { "Builder successfully revealed payload" ) } - Ok(None) => { - info!( - relay_response_ms = duration.as_millis(), - ?block_root, - "Builder returned a successful response" - ); - } Err(e) => { metrics::inc_counter_vec( &metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME, @@ -1968,6 +1982,66 @@ impl ExecutionLayer { Err(Error::NoPayloadBuilder) } } + + async fn post_builder_blinded_blocks_v2( + &self, + block_root: Hash256, + block: &SignedBlindedBeaconBlock, + ) -> Result<(), Error> { + if let Some(builder) = self.builder() { + let (result, duration) = timed_future(metrics::POST_BLINDED_PAYLOAD_BUILDER, async { + let ssz_enabled = builder.is_ssz_available(); + debug!( + ?block_root, + ssz = ssz_enabled, + "Calling submit_blinded_block v2 on builder" + ); + if ssz_enabled { + builder + .post_builder_blinded_blocks_v2_ssz(block) + .await + .map_err(Error::Builder) + } else { + builder + .post_builder_blinded_blocks_v2(block) + .await + .map_err(Error::Builder) + } + }) + .await; + + match result { + Ok(()) => { + metrics::inc_counter_vec( + &metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME, + &[metrics::SUCCESS], + ); + info!( + relay_response_ms = duration.as_millis(), + ?block_root, + "Successfully submitted blinded block to the builder" + ) + } + Err(e) => { + metrics::inc_counter_vec( + &metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME, + &[metrics::FAILURE], + ); + error!( + info = "this may result in a missed block proposal", + error = ?e, + relay_response_ms = duration.as_millis(), + ?block_root, + "Failed to submit blinded block to the builder" + ) + } + } + + Ok(()) + } else { + Err(Error::NoPayloadBuilder) + } + } } #[derive(AsRefStr)] diff --git a/beacon_node/http_api/src/publish_blocks.rs b/beacon_node/http_api/src/publish_blocks.rs index 5d581859ae9..c1b86416b15 100644 --- a/beacon_node/http_api/src/publish_blocks.rs +++ b/beacon_node/http_api/src/publish_blocks.rs @@ -13,7 +13,7 @@ use eth2::types::{ BlobsBundle, BroadcastValidation, ErrorMessage, ExecutionPayloadAndBlobs, FullPayloadContents, PublishBlockRequest, SignedBlockContents, }; -use execution_layer::ProvenancedPayload; +use execution_layer::{ProvenancedPayload, SubmitBlindedBlockResponse}; use futures::TryFutureExt; use lighthouse_network::{NetworkGlobals, PubsubMessage}; use network::NetworkMessage; @@ -636,27 +636,37 @@ pub async fn publish_blinded_block( network_globals: Arc>, ) -> Result { let block_root = blinded_block.canonical_root(); - let full_block = reconstruct_block(chain.clone(), block_root, blinded_block).await?; - publish_block::( - Some(block_root), - full_block, - chain, - network_tx, - validation_level, - duplicate_status_code, - network_globals, - ) - .await + let full_block_opt = reconstruct_block(chain.clone(), block_root, blinded_block).await?; + + if let Some(full_block) = full_block_opt { + publish_block::( + Some(block_root), + full_block, + chain, + network_tx, + validation_level, + duplicate_status_code, + network_globals, + ) + .await + } else { + // From the fulu fork, builders are responsible for publishing and + // will no longer return the full payload and blobs. + Ok(warp::reply().into_response()) + } } /// Deconstruct the given blinded block, and construct a full block. This attempts to use the /// execution layer's payload cache, and if that misses, attempts a blind block proposal to retrieve /// the full payload. +/// +/// From the Fulu fork, external builders no longer return the full payload and blobs, and this +/// function will always return `Ok(None)` on successful submission of blinded block. pub async fn reconstruct_block( chain: Arc>, block_root: Hash256, block: Arc>, -) -> Result>>, Rejection> { +) -> Result>>>, Rejection> { let full_payload_opt = if let Ok(payload_header) = block.message().body().execution_payload() { let el = chain.execution_layer.as_ref().ok_or_else(|| { warp_utils::reject::custom_server_error("Missing execution layer".to_string()) @@ -696,17 +706,24 @@ pub async fn reconstruct_block( "builder", ); - let full_payload = el - .propose_blinded_beacon_block(block_root, &block) + match el + .propose_blinded_beacon_block(block_root, &block, &chain.spec) .await .map_err(|e| { warp_utils::reject::custom_server_error(format!( "Blind block proposal failed: {:?}", e )) - })?; - info!(block_hash = ?full_payload.block_hash(), "Successfully published a block to the builder network"); - ProvenancedPayload::Builder(full_payload) + })? { + SubmitBlindedBlockResponse::V1(full_payload) => { + info!(block_root = ?block_root, "Successfully published a block to the builder network"); + ProvenancedPayload::Builder(*full_payload) + } + SubmitBlindedBlockResponse::V2 => { + info!(block_root = ?block_root, "Successfully published a block to the builder network"); + return Ok(None); + } + } }; Some(full_payload_contents) @@ -734,6 +751,7 @@ pub async fn reconstruct_block( .map(|(block, blobs)| ProvenancedBlock::builder(block, blobs)) } } + .map(Some) .map_err(|e| { warp_utils::reject::custom_server_error(format!("Unable to add payload to block: {e:?}")) }) From 3b8d8e762d6b81f6bcfa1c26b3af29a6d5914951 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 23 Jul 2025 15:23:25 +1000 Subject: [PATCH 6/8] Fix tests. --- beacon_node/http_api/tests/broadcast_validation_tests.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/beacon_node/http_api/tests/broadcast_validation_tests.rs b/beacon_node/http_api/tests/broadcast_validation_tests.rs index 27831b3a232..95c21d8fe2f 100644 --- a/beacon_node/http_api/tests/broadcast_validation_tests.rs +++ b/beacon_node/http_api/tests/broadcast_validation_tests.rs @@ -1275,14 +1275,17 @@ pub async fn blinded_equivocation_consensus_late_equivocation() { Arc::new(block_a), ) .await - .unwrap(); + .expect("failed to reconstruct block") + .expect("block expected"); + let unblinded_block_b = reconstruct_block( tester.harness.chain.clone(), block_b.canonical_root(), block_b.clone(), ) .await - .unwrap(); + .expect("failed to reconstruct block") + .expect("block expected"); let inner_block_a = match unblinded_block_a { ProvenancedBlock::Local(a, _, _) => a, From 8daa4526f09745352b721352bbeceb814947de0a Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Fri, 25 Jul 2025 14:07:57 +1000 Subject: [PATCH 7/8] Clean up --- beacon_node/http_api/src/lib.rs | 2 +- beacon_node/http_api/tests/tests.rs | 2 +- consensus/types/src/config_and_preset.rs | 30 ++++--------------- .../src/mock_beacon_node.rs | 2 +- validator_client/http_api/src/lib.rs | 2 +- validator_client/http_api/src/test_utils.rs | 2 +- validator_client/http_api/src/tests.rs | 9 +++--- 7 files changed, 15 insertions(+), 34 deletions(-) diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 83422090caa..c66ddacdaf6 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -2763,7 +2763,7 @@ pub fn serve( move |task_spawner: TaskSpawner, chain: Arc>| { task_spawner.blocking_json_task(Priority::P0, move || { let config_and_preset = - ConfigAndPreset::from_chain_spec::(&chain.spec, None); + ConfigAndPreset::from_chain_spec::(&chain.spec); Ok(api_types::GenericResponse::from(config_and_preset)) }) }, diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index f6dc435e607..71ffdb75e76 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -2628,7 +2628,7 @@ impl ApiTester { .map(|res| ConfigAndPreset::Electra(res.data)) } .unwrap(); - let expected = ConfigAndPreset::from_chain_spec::(&self.chain.spec, None); + let expected = ConfigAndPreset::from_chain_spec::(&self.chain.spec); assert_eq!(result, expected); diff --git a/consensus/types/src/config_and_preset.rs b/consensus/types/src/config_and_preset.rs index 84339b11b61..94cdb28cd54 100644 --- a/consensus/types/src/config_and_preset.rs +++ b/consensus/types/src/config_and_preset.rs @@ -1,6 +1,6 @@ use crate::{ consts::altair, consts::deneb, AltairPreset, BasePreset, BellatrixPreset, CapellaPreset, - ChainSpec, Config, DenebPreset, ElectraPreset, EthSpec, ForkName, FuluPreset, + ChainSpec, Config, DenebPreset, ElectraPreset, EthSpec, FuluPreset, }; use maplit::hashmap; use serde::{Deserialize, Serialize}; @@ -43,7 +43,7 @@ pub struct ConfigAndPreset { } impl ConfigAndPreset { - pub fn from_chain_spec(spec: &ChainSpec, fork_name: Option) -> Self { + pub fn from_chain_spec(spec: &ChainSpec) -> Self { let config = Config::from_chain_spec::(spec); let base_preset = BasePreset::from_chain_spec::(spec); let altair_preset = AltairPreset::from_chain_spec::(spec); @@ -52,13 +52,7 @@ impl ConfigAndPreset { let deneb_preset = DenebPreset::from_chain_spec::(spec); let extra_fields = get_extra_fields(spec); - // The latest stable fork is used if the `fork_name` is not specified. - let latest_stable_fork = ForkName::latest_stable(); - - if spec.fulu_fork_epoch.is_some() - || latest_stable_fork == ForkName::Fulu - || fork_name == Some(ForkName::Fulu) - { + if spec.is_fulu_scheduled() { let electra_preset = ElectraPreset::from_chain_spec::(spec); let fulu_preset = FuluPreset::from_chain_spec::(spec); @@ -73,10 +67,7 @@ impl ConfigAndPreset { fulu_preset, extra_fields, }) - } else if spec.electra_fork_epoch.is_some() - || latest_stable_fork == ForkName::Electra - || fork_name == Some(ForkName::Electra) - { + } else { let electra_preset = ElectraPreset::from_chain_spec::(spec); ConfigAndPreset::Electra(ConfigAndPresetElectra { @@ -89,16 +80,6 @@ impl ConfigAndPreset { electra_preset, extra_fields, }) - } else { - ConfigAndPreset::Deneb(ConfigAndPresetDeneb { - config, - base_preset, - altair_preset, - bellatrix_preset, - capella_preset, - deneb_preset, - extra_fields, - }) } } } @@ -155,8 +136,7 @@ mod test { .open(tmp_file.as_ref()) .expect("error opening file"); let mainnet_spec = ChainSpec::mainnet(); - let mut yamlconfig = - ConfigAndPreset::from_chain_spec::(&mainnet_spec, None); + let mut yamlconfig = ConfigAndPreset::from_chain_spec::(&mainnet_spec); let (k1, v1) = ("SAMPLE_HARDFORK_KEY1", "123456789"); let (k2, v2) = ("SAMPLE_HARDFORK_KEY2", "987654321"); let (k3, v3) = ("SAMPLE_HARDFORK_KEY3", 32); diff --git a/testing/validator_test_rig/src/mock_beacon_node.rs b/testing/validator_test_rig/src/mock_beacon_node.rs index 7a902709138..ff1e772d544 100644 --- a/testing/validator_test_rig/src/mock_beacon_node.rs +++ b/testing/validator_test_rig/src/mock_beacon_node.rs @@ -41,7 +41,7 @@ impl MockBeaconNode { pub fn mock_config_spec(&mut self, spec: &ChainSpec) { let path_pattern = Regex::new(r"^/eth/v1/config/spec$").unwrap(); - let config_and_preset = ConfigAndPreset::from_chain_spec::(spec, None); + let config_and_preset = ConfigAndPreset::from_chain_spec::(spec); let data = GenericResponse::from(config_and_preset); self.server .mock("GET", Matcher::Regex(path_pattern.to_string())) diff --git a/validator_client/http_api/src/lib.rs b/validator_client/http_api/src/lib.rs index d5de24229c4..02a677212cb 100644 --- a/validator_client/http_api/src/lib.rs +++ b/validator_client/http_api/src/lib.rs @@ -315,7 +315,7 @@ pub fn serve( .and(spec_filter.clone()) .then(|spec: Arc<_>| { blocking_json_task(move || { - let config = ConfigAndPreset::from_chain_spec::(&spec, None); + let config = ConfigAndPreset::from_chain_spec::(&spec); Ok(api_types::GenericResponse::from(config)) }) }); diff --git a/validator_client/http_api/src/test_utils.rs b/validator_client/http_api/src/test_utils.rs index feb71c3a467..53bcf7baebb 100644 --- a/validator_client/http_api/src/test_utils.rs +++ b/validator_client/http_api/src/test_utils.rs @@ -260,7 +260,7 @@ impl ApiTester { .await .map(|res| ConfigAndPreset::Fulu(res.data)) .unwrap(); - let expected = ConfigAndPreset::from_chain_spec::(&E::default_spec(), None); + let expected = ConfigAndPreset::from_chain_spec::(&E::default_spec()); assert_eq!(result, expected); diff --git a/validator_client/http_api/src/tests.rs b/validator_client/http_api/src/tests.rs index d1e682568ba..b021186e77a 100644 --- a/validator_client/http_api/src/tests.rs +++ b/validator_client/http_api/src/tests.rs @@ -45,6 +45,7 @@ struct ApiTester { validator_store: Arc>, url: SensitiveUrl, slot_clock: TestingSlotClock, + spec: Arc, _validator_dir: TempDir, _secrets_dir: TempDir, _test_runtime: TestRuntime, @@ -117,7 +118,7 @@ impl ApiTester { validator_store: Some(validator_store.clone()), graffiti_file: None, graffiti_flag: Some(Graffiti::default()), - spec: E::default_spec().into(), + spec: spec.clone(), config: HttpConfig { enabled: true, listen_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), @@ -152,6 +153,7 @@ impl ApiTester { validator_store, url, slot_clock, + spec, _validator_dir: validator_dir, _secrets_dir: secrets_dir, _test_runtime: test_runtime, @@ -206,8 +208,7 @@ impl ApiTester { } pub async fn test_get_lighthouse_spec(self) -> Self { - let spec = E::default_spec(); - let result = if spec.fulu_fork_epoch.is_some() { + let result = if self.spec.is_fulu_scheduled() { self.client .get_lighthouse_spec::() .await @@ -219,7 +220,7 @@ impl ApiTester { .map(|res| ConfigAndPreset::Electra(res.data)) } .unwrap(); - let expected = ConfigAndPreset::from_chain_spec::(&spec, None); + let expected = ConfigAndPreset::from_chain_spec::(&self.spec); assert_eq!(result, expected); From ad99f18defe1556e4516352876b15201aec27608 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Fri, 25 Jul 2025 14:40:41 +1000 Subject: [PATCH 8/8] Fix merge snafus --- beacon_node/http_api/tests/tests.rs | 2 +- consensus/types/src/config_and_preset.rs | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 71ffdb75e76..5ac8cd91864 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -2616,7 +2616,7 @@ impl ApiTester { } pub async fn test_get_config_spec(self) -> Self { - let result = if self.chain.spec.fulu_fork_epoch.is_some() { + let result = if self.chain.spec.is_fulu_scheduled() { self.client .get_config_spec::() .await diff --git a/consensus/types/src/config_and_preset.rs b/consensus/types/src/config_and_preset.rs index e64b5e3e1b5..cf5cff8ea67 100644 --- a/consensus/types/src/config_and_preset.rs +++ b/consensus/types/src/config_and_preset.rs @@ -52,11 +52,6 @@ impl ConfigAndPreset { let deneb_preset = DenebPreset::from_chain_spec::(spec); let extra_fields = get_extra_fields(spec); - // Remove blob schedule for backwards-compatibility. - if spec.is_fulu_scheduled() { - config.blob_schedule.set_skip_serializing(); - } - if spec.is_fulu_scheduled() { let electra_preset = ElectraPreset::from_chain_spec::(spec); let fulu_preset = FuluPreset::from_chain_spec::(spec); @@ -73,6 +68,9 @@ impl ConfigAndPreset { extra_fields, }) } else { + // Remove blob schedule for backwards-compatibility. + config.blob_schedule.set_skip_serializing(); + let electra_preset = ElectraPreset::from_chain_spec::(spec); ConfigAndPreset::Electra(ConfigAndPresetElectra { @@ -160,8 +158,8 @@ mod test { .write(false) .open(tmp_file.as_ref()) .expect("error while opening the file"); - let from: ConfigAndPresetElectra = + let from: ConfigAndPresetFulu = serde_yaml::from_reader(reader).expect("error while deserializing"); - assert_eq!(ConfigAndPreset::Electra(from), yamlconfig); + assert_eq!(ConfigAndPreset::Fulu(from), yamlconfig); } }