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

@@ -4807,7 +4807,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 +4824,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);
@@ -4894,6 +4896,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 +4929,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 +8620,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()