This commit is contained in:
Eitan Seri-Levi
2026-05-12 15:23:11 +03:00
158 changed files with 1464 additions and 1572 deletions

View File

@@ -2,8 +2,9 @@ use crate::{
build_block_contents,
version::{
ResponseIncludesVersion, add_consensus_block_value_header, add_consensus_version_header,
add_execution_payload_blinded_header, add_execution_payload_value_header,
add_ssz_content_type_header, beacon_response, inconsistent_fork_rejection,
add_execution_payload_blinded_header, add_execution_payload_included_header,
add_execution_payload_value_header, add_ssz_content_type_header, beacon_response,
inconsistent_fork_rejection,
},
};
use beacon_chain::graffiti_calculator::GraffitiSettings;
@@ -83,7 +84,16 @@ pub async fn produce_block_v4<T: BeaconChainTypes>(
warp_utils::reject::custom_bad_request(format!("failed to fetch a block: {:?}", e))
})?;
build_response_v4::<T>(block, consensus_block_value, accept_header, &chain.spec)
// TODO(gloas): wire up for stateless mode (#8828).
let execution_payload_included = false;
build_response_v4::<T>(
block,
consensus_block_value,
execution_payload_included,
accept_header,
&chain.spec,
)
}
#[instrument(
@@ -133,6 +143,7 @@ pub async fn produce_block_v3<T: BeaconChainTypes>(
pub fn build_response_v4<T: BeaconChainTypes>(
block: BeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>,
consensus_block_value: u64,
execution_payload_included: bool,
accept_header: Option<api_types::Accept>,
spec: &ChainSpec,
) -> Result<Response<Body>, warp::Rejection> {
@@ -146,6 +157,7 @@ pub fn build_response_v4<T: BeaconChainTypes>(
let metadata = ProduceBlockV4Metadata {
consensus_version: fork_name,
consensus_block_value: consensus_block_value_wei,
execution_payload_included,
};
match accept_header {
@@ -155,6 +167,7 @@ pub fn build_response_v4<T: BeaconChainTypes>(
.map(|res: Response<Body>| add_ssz_content_type_header(res))
.map(|res: Response<Body>| add_consensus_version_header(res, fork_name))
.map(|res| add_consensus_block_value_header(res, consensus_block_value_wei))
.map(|res| add_execution_payload_included_header(res, execution_payload_included))
.map_err(|e| -> warp::Rejection {
warp_utils::reject::custom_server_error(format!("failed to create response: {}", e))
}),
@@ -165,7 +178,8 @@ pub fn build_response_v4<T: BeaconChainTypes>(
})
.into_response())
.map(|res| add_consensus_version_header(res, fork_name))
.map(|res| add_consensus_block_value_header(res, consensus_block_value_wei)),
.map(|res| add_consensus_block_value_header(res, consensus_block_value_wei))
.map(|res| add_execution_payload_included_header(res, execution_payload_included)),
}
}

View File

@@ -12,7 +12,7 @@ use types::Slot;
use warp::http::Response;
use warp::{Filter, Rejection};
// GET validator/execution_payload_envelope/{slot}/{builder_index}
// GET validator/execution_payload_envelope/{slot}
pub fn get_validator_execution_payload_envelope<T: BeaconChainTypes>(
eth_v1: EthV1Filter,
chain_filter: ChainFilter<T>,
@@ -27,11 +27,6 @@ pub fn get_validator_execution_payload_envelope<T: BeaconChainTypes>(
"Invalid slot".to_string(),
))
}))
.and(warp::path::param::<u64>().or_else(|_| async {
Err(warp_utils::reject::custom_bad_request(
"Invalid builder_index".to_string(),
))
}))
.and(warp::path::end())
.and(warp::header::optional::<Accept>("accept"))
.and(not_while_syncing_filter)
@@ -39,10 +34,6 @@ pub fn get_validator_execution_payload_envelope<T: BeaconChainTypes>(
.and(chain_filter)
.then(
|slot: Slot,
// TODO(gloas) we're only doing local building
// we'll need to implement builder index logic
// eventually.
_builder_index: u64,
accept_header: Option<Accept>,
not_synced_filter: Result<(), Rejection>,
task_spawner: TaskSpawner<T::EthSpec>,

View File

@@ -333,8 +333,12 @@ pub fn get_validator_payload_attestation_data<T: BeaconChainTypes>(
let payload_attestation_data = chain
.produce_payload_attestation_data(slot)
.map_err(|e| match e {
BeaconChainError::InvalidSlot(_)
| BeaconChainError::NoBlockForSlot(_) => {
BeaconChainError::NoBlockForSlot(_) => {
warp_utils::reject::block_not_found(format!(
"No block received for slot {slot}"
))
}
BeaconChainError::InvalidSlot(_) => {
warp_utils::reject::custom_bad_request(format!(
"Unable to produce payload attestation data: {e:?}"
))

View File

@@ -5,7 +5,8 @@ use eth2::beacon_response::{
};
use eth2::{
CONSENSUS_BLOCK_VALUE_HEADER, CONSENSUS_VERSION_HEADER, CONTENT_TYPE_HEADER,
EXECUTION_PAYLOAD_BLINDED_HEADER, EXECUTION_PAYLOAD_VALUE_HEADER, SSZ_CONTENT_TYPE_HEADER,
EXECUTION_PAYLOAD_BLINDED_HEADER, EXECUTION_PAYLOAD_INCLUDED_HEADER,
EXECUTION_PAYLOAD_VALUE_HEADER, SSZ_CONTENT_TYPE_HEADER,
};
use serde::Serialize;
use types::{ForkName, InconsistentFork, Uint256};
@@ -88,6 +89,19 @@ pub fn add_execution_payload_blinded_header<T: Reply>(
.into_response()
}
/// Add the `Eth-Execution-Payload-Included` header to a response.
pub fn add_execution_payload_included_header<T: Reply>(
reply: T,
execution_payload_included: bool,
) -> Response {
reply::with_header(
reply,
EXECUTION_PAYLOAD_INCLUDED_HEADER,
execution_payload_included.to_string(),
)
.into_response()
}
/// Add the `Eth-Execution-Payload-Value` header to a response.
pub fn add_execution_payload_value_header<T: Reply>(
reply: T,

View File

@@ -57,14 +57,9 @@ async fn sync_committee_duties_across_fork() {
// If there's a skip slot at the fork slot, the endpoint should return duties, even
// though the head state hasn't transitioned yet.
let fork_slot = fork_epoch.start_slot(E::slots_per_epoch());
let (genesis_state, genesis_state_root) = harness.get_current_state_and_root();
let (_, mut state) = harness
.add_attested_block_at_slot(
fork_slot - 1,
genesis_state,
genesis_state_root,
&all_validators,
)
let genesis_state = harness.get_current_state();
let (_, state) = harness
.add_attested_block_at_slot(fork_slot - 1, genesis_state, &all_validators)
.await
.unwrap();
@@ -79,9 +74,8 @@ async fn sync_committee_duties_across_fork() {
assert_eq!(sync_duties.len(), E::sync_committee_size());
// After applying a block at the fork slot the duties should remain unchanged.
let state_root = state.canonical_root().unwrap();
harness
.add_attested_block_at_slot(fork_slot, state, state_root, &all_validators)
.add_attested_block_at_slot(fork_slot, state, &all_validators)
.await
.unwrap();
@@ -295,14 +289,9 @@ async fn sync_committee_indices_across_fork() {
// If there's a skip slot at the fork slot, the endpoint will return a 400 until a block is
// applied.
let fork_slot = fork_epoch.start_slot(E::slots_per_epoch());
let (genesis_state, genesis_state_root) = harness.get_current_state_and_root();
let (_, mut state) = harness
.add_attested_block_at_slot(
fork_slot - 1,
genesis_state,
genesis_state_root,
&all_validators,
)
let genesis_state = harness.get_current_state();
let (_, state) = harness
.add_attested_block_at_slot(fork_slot - 1, genesis_state, &all_validators)
.await
.unwrap();
@@ -334,9 +323,8 @@ async fn sync_committee_indices_across_fork() {
// Once the head is updated it should be useable for requests, including in the next sync
// committee period.
let state_root = state.canonical_root().unwrap();
harness
.add_attested_block_at_slot(fork_slot + 1, state, state_root, &all_validators)
.add_attested_block_at_slot(fork_slot + 1, state, &all_validators)
.await
.unwrap();

View File

@@ -4288,6 +4288,7 @@ impl ApiTester {
);
// TODO(gloas): check why consensus block value is 0
// assert!(!metadata.consensus_block_value.is_zero());
assert!(!metadata.execution_payload_included);
let block_root = block.tree_hash_root();
let envelope = self
@@ -4360,7 +4361,7 @@ impl ApiTester {
let (response, metadata) = self
.client
.get_validator_blocks_v4::<E>(slot, &randao_reveal, None, None, None)
.get_validator_blocks_v4::<E>(slot, &randao_reveal, None, None, None, None)
.await
.unwrap();
let block = response.data;
@@ -4369,7 +4370,7 @@ impl ApiTester {
let envelope = self
.client
.get_validator_execution_payload_envelope::<E>(slot, BUILDER_INDEX_SELF_BUILD)
.get_validator_execution_payload_envelope::<E>(slot)
.await
.unwrap()
.data;
@@ -4423,7 +4424,7 @@ impl ApiTester {
let (block, metadata) = self
.client
.get_validator_blocks_v4_ssz::<E>(slot, &randao_reveal, None, None, None)
.get_validator_blocks_v4_ssz::<E>(slot, &randao_reveal, None, None, None, None)
.await
.unwrap();
@@ -4431,7 +4432,7 @@ impl ApiTester {
let envelope = self
.client
.get_validator_execution_payload_envelope_ssz::<E>(slot, BUILDER_INDEX_SELF_BUILD)
.get_validator_execution_payload_envelope_ssz::<E>(slot)
.await
.unwrap();
@@ -4807,7 +4808,8 @@ impl ApiTester {
.client
.get_validator_payload_attestation_data(slot)
.await
.unwrap();
.unwrap()
.expect("expected payload attestation data for slot with block");
assert_eq!(response.version(), Some(fork_name));
@@ -4823,7 +4825,8 @@ impl ApiTester {
.client
.get_validator_payload_attestation_data_ssz(slot)
.await
.unwrap();
.unwrap()
.expect("expected SSZ payload attestation data for slot with block");
assert_eq!(ssz_result, expected);
@@ -4859,7 +4862,7 @@ impl ApiTester {
// Produce and publish a block.
let (response, _metadata) = self
.client
.get_validator_blocks_v4::<E>(slot, &randao_reveal, None, None, None)
.get_validator_blocks_v4::<E>(slot, &randao_reveal, None, None, None, None)
.await
.unwrap();
let block = response.data;
@@ -4876,7 +4879,7 @@ impl ApiTester {
// Retrieve and publish the envelope.
let envelope = self
.client
.get_validator_execution_payload_envelope::<E>(slot, BUILDER_INDEX_SELF_BUILD)
.get_validator_execution_payload_envelope::<E>(slot)
.await
.unwrap()
.data;
@@ -4894,6 +4897,7 @@ impl ApiTester {
.get_validator_payload_attestation_data(slot)
.await
.unwrap()
.expect("expected payload attestation data for slot with block")
.into_data();
assert_eq!(pa_data.beacon_block_root, block_root);
@@ -4926,6 +4930,26 @@ impl ApiTester {
self
}
pub async fn test_get_validator_payload_attestation_data_no_block(self) -> Self {
// Advance the slot clock without producing a block
self.harness.advance_slot();
let slot = self.chain.slot().unwrap();
// Should return None when no block exists for the slot
let result = self
.client
.get_validator_payload_attestation_data(slot)
.await
.unwrap();
assert!(
result.is_none(),
"expected None for empty slot, got: {result:?}"
);
self
}
#[allow(clippy::await_holding_lock)] // This is a test, so it should be fine.
pub async fn test_get_validator_aggregate_attestation_v1(self) -> Self {
let attestation = self
@@ -8597,6 +8621,17 @@ async fn get_validator_payload_attestation_data_pre_gloas() {
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn get_validator_payload_attestation_data_no_block() {
if !fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
return;
}
ApiTester::new_with_hard_forks()
.await
.test_get_validator_payload_attestation_data_no_block()
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn payload_attestation_present_after_envelope_publish() {
ApiTester::new_with_hard_forks()