fix: payload_attestation_data when no block received for slot (#9225)

Addresses issue #9220

The `payload_attestation_data` endpoint returns 400 when no block has been received for the requested slot. This causes the VC to log at CRIT level for what is expected behaviour per spec: validators should simply not submit a payload attestation when no block has been seen.


  - Return 404 (Not Found) instead of 400 from `payload_attestation_data` when no block exists for the slot. This is consistent with other beacon api endpoints.
- Downgrade the VC log from `crit` to `debug` when a 503 is received, since this is an expected no-op per spec.
- Add `BlockNotFound` rejection type to `warp_utils`.
- Add a test asserting the 404 response for an empty slot.


Co-Authored-By: Josh King <josh@sigmaprime.io>

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

Co-Authored-By: Jimmy Chen <jchen.tc@gmail.com>
This commit is contained in:
jking-aus
2026-05-05 01:39:33 +02:00
committed by GitHub
parent 9cf155a0dd
commit d9be76afe7
5 changed files with 83 additions and 15 deletions

View File

@@ -3030,10 +3030,11 @@ impl BeaconNodeHttpClient {
}
/// `GET validator/payload_attestation_data/{slot}`
/// Returns `None` if no block has been received for the requested slot (404).
pub async fn get_validator_payload_attestation_data(
&self,
slot: Slot,
) -> Result<BeaconResponse<PayloadAttestationData>, Error> {
) -> Result<Option<BeaconResponse<PayloadAttestationData>>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
@@ -3042,16 +3043,23 @@ impl BeaconNodeHttpClient {
.push("payload_attestation_data")
.push(&slot.to_string());
self.get_with_timeout(path, self.timeouts.payload_attestation)
let opt_response = self
.get_response(path, |b| b.timeout(self.timeouts.payload_attestation))
.await
.map(BeaconResponse::ForkVersioned)
.optional()?;
match opt_response {
Some(response) => Ok(Some(BeaconResponse::ForkVersioned(response.json().await?))),
None => Ok(None),
}
}
/// `GET validator/payload_attestation_data/{slot}` in SSZ format
/// Returns `None` if no block has been received for the requested slot (404).
pub async fn get_validator_payload_attestation_data_ssz(
&self,
slot: Slot,
) -> Result<PayloadAttestationData, Error> {
) -> Result<Option<PayloadAttestationData>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
@@ -3064,9 +3072,9 @@ impl BeaconNodeHttpClient {
.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.payload_attestation)
.await?;
let response_bytes = opt_response.ok_or(Error::StatusCode(StatusCode::NOT_FOUND))?;
PayloadAttestationData::from_ssz_bytes(&response_bytes).map_err(Error::InvalidSsz)
opt_response
.map(|bytes| PayloadAttestationData::from_ssz_bytes(&bytes).map_err(Error::InvalidSsz))
.transpose()
}
/// `GET v1/validator/aggregate_attestation?slot,attestation_data_root`