Gloas - add get_payload_attestation_endpoint (#8497)

Co-Authored-By: shane-moore <skm1790@gmail.com>

Co-Authored-By: Eitan Seri- Levi <eserilev@gmail.com>

Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu>

Co-Authored-By: Jimmy Chen <jchen.tc@gmail.com>
This commit is contained in:
Shane K Moore
2026-04-17 07:01:25 -07:00
committed by GitHub
parent 4cb3ffed8d
commit b561b59549
7 changed files with 283 additions and 1 deletions

View File

@@ -2536,6 +2536,14 @@ pub fn serve<T: BeaconChainTypes>(
task_spawner_filter.clone(),
);
// GET validator/payload_attestation_data/{slot}
let get_validator_payload_attestation_data = get_validator_payload_attestation_data(
eth_v1.clone(),
chain_filter.clone(),
not_while_syncing_filter.clone(),
task_spawner_filter.clone(),
);
// GET validator/aggregate_attestation?attestation_data_root,slot
let get_validator_aggregate_attestation = get_validator_aggregate_attestation(
any_version.clone(),
@@ -3347,6 +3355,7 @@ pub fn serve<T: BeaconChainTypes>(
.uor(get_validator_blinded_blocks)
.uor(get_validator_execution_payload_envelope)
.uor(get_validator_attestation_data)
.uor(get_validator_payload_attestation_data)
.uor(get_validator_aggregate_attestation)
.uor(get_validator_sync_committee_contribution)
.uor(get_lighthouse_health)

View File

@@ -248,6 +248,106 @@ pub fn get_validator_attestation_data<T: BeaconChainTypes>(
.boxed()
}
// GET validator/payload_attestation_data/{slot}
pub fn get_validator_payload_attestation_data<T: BeaconChainTypes>(
eth_v1: EthV1Filter,
chain_filter: ChainFilter<T>,
not_while_syncing_filter: NotWhileSyncingFilter,
task_spawner_filter: TaskSpawnerFilter<T>,
) -> ResponseFilter {
use eth2::beacon_response::{EmptyMetadata, ForkVersionedResponse};
use ssz::Encode;
use warp::http::Response;
eth_v1
.and(warp::path("validator"))
.and(warp::path("payload_attestation_data"))
.and(warp::path::param::<Slot>().or_else(|_| async {
Err(warp_utils::reject::custom_bad_request(
"Invalid slot".to_string(),
))
}))
.and(warp::path::end())
.and(warp::header::optional::<Accept>("accept"))
.and(not_while_syncing_filter)
.and(task_spawner_filter)
.and(chain_filter)
.then(
|slot: Slot,
accept_header: Option<Accept>,
not_synced_filter: Result<(), Rejection>,
task_spawner: TaskSpawner<T::EthSpec>,
chain: Arc<BeaconChain<T>>| {
task_spawner.blocking_response_task(Priority::P0, move || {
not_synced_filter?;
let fork_name = chain.spec.fork_name_at_slot::<T::EthSpec>(slot);
// Payload attestations are only valid for Gloas and later forks
if !fork_name.gloas_enabled() {
return Err(warp_utils::reject::custom_bad_request(format!(
"Payload attestations are not supported for fork: {fork_name}"
)));
}
let payload_attestation_data = chain
.produce_payload_attestation_data(slot)
.map_err(|e| match e {
BeaconChainError::InvalidSlot(_)
| BeaconChainError::NoBlockForSlot(_) => {
warp_utils::reject::custom_bad_request(format!(
"Unable to produce payload attestation data: {e:?}"
))
}
_ => warp_utils::reject::custom_server_error(format!(
"Unable to produce payload attestation data: {e:?}"
)),
})?;
match accept_header {
Some(Accept::Ssz) => Response::builder()
.status(200)
.header("Content-Type", "application/octet-stream")
.header("Eth-Consensus-Version", fork_name.to_string())
.body(payload_attestation_data.as_ssz_bytes().into())
.map(|res: Response<warp::hyper::Body>| res)
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
"Failed to build SSZ response: {e}"
))
}),
_ => {
let json_response = ForkVersionedResponse {
version: fork_name,
metadata: EmptyMetadata {},
data: payload_attestation_data,
};
Response::builder()
.status(200)
.header("Content-Type", "application/json")
.header("Eth-Consensus-Version", fork_name.to_string())
.body(
serde_json::to_string(&json_response)
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
"Failed to serialize response: {e}"
))
})?
.into(),
)
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
"Failed to build JSON response: {e}"
))
})
}
}
})
},
)
.boxed()
}
// GET validator/blinded_blocks/{slot}
pub fn get_validator_blinded_blocks<T: BeaconChainTypes>(
eth_v1: EthV1Filter,

View File

@@ -3,7 +3,8 @@ use beacon_chain::test_utils::RelativeSyncCommittee;
use beacon_chain::{
BeaconChain, ChainConfig, StateSkipConfig, WhenSlotSkipped,
test_utils::{
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType, test_spec,
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
fork_name_from_env, test_spec,
},
};
use bls::{AggregateSignature, Keypair, PublicKeyBytes, SecretKey, Signature, SignatureBytes};
@@ -4434,6 +4435,53 @@ impl ApiTester {
self
}
pub async fn test_get_validator_payload_attestation_data(self) -> Self {
let slot = self.chain.slot().unwrap();
let fork_name = self.chain.spec.fork_name_at_slot::<E>(slot);
let response = self
.client
.get_validator_payload_attestation_data(slot)
.await
.unwrap();
assert_eq!(response.version(), Some(fork_name));
let result = response.into_data();
let expected = self.chain.produce_payload_attestation_data(slot).unwrap();
assert_eq!(result.beacon_block_root, expected.beacon_block_root);
assert_eq!(result.slot, expected.slot);
assert_eq!(result.payload_present, expected.payload_present);
assert_eq!(result.blob_data_available, expected.blob_data_available);
let ssz_result = self
.client
.get_validator_payload_attestation_data_ssz(slot)
.await
.unwrap();
assert_eq!(ssz_result, expected);
self
}
pub async fn test_get_validator_payload_attestation_data_pre_gloas(self) -> Self {
let slot = self.chain.slot().unwrap();
// The endpoint should return a 400 error for pre-Gloas forks
match self
.client
.get_validator_payload_attestation_data(slot)
.await
{
Ok(result) => panic!("query for pre-Gloas slot should fail, got: {result:?}"),
Err(e) => assert_eq!(e.status().unwrap(), 400),
}
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
@@ -8057,6 +8105,30 @@ async fn get_validator_attestation_data_with_skip_slots() {
.await;
}
// TODO(EIP-7732): Remove `#[ignore]` once gloas beacon chain harness is implemented
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn get_validator_payload_attestation_data() {
if !fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
return;
}
ApiTester::new()
.await
.test_get_validator_payload_attestation_data()
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn get_validator_payload_attestation_data_pre_gloas() {
if fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
return;
}
ApiTester::new()
.await
.test_get_validator_payload_attestation_data_pre_gloas()
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn get_validator_aggregate_attestation_v1() {
ApiTester::new()