Skip to content

Commit 9870dd2

Browse files
committed
add get_payload_attestation_endpoint
1 parent 4c9d327 commit 9870dd2

File tree

6 files changed

+184
-0
lines changed

6 files changed

+184
-0
lines changed

beacon_node/beacon_chain/src/beacon_chain.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2067,6 +2067,45 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
20672067
)?)
20682068
}
20692069

2070+
/// Produce a `PayloadAttestationData` for a PTC validator to sign.
2071+
///
2072+
/// This is used by PTC (Payload Timeliness Committee) validators to attest to the
2073+
/// presence/absence of an execution payload and blobs for a given slot.
2074+
pub fn produce_payload_attestation_data(
2075+
&self,
2076+
request_slot: Slot,
2077+
) -> Result<PayloadAttestationData, Error> {
2078+
let _timer = metrics::start_timer(&metrics::PAYLOAD_ATTESTATION_PRODUCTION_SECONDS);
2079+
2080+
// Payload attestations are only valid for the current slot
2081+
let current_slot = self.slot()?;
2082+
if request_slot != current_slot {
2083+
return Err(Error::InvalidSlot(request_slot));
2084+
}
2085+
2086+
// Check if we've seen a block for this slot from the canonical head
2087+
let head = self.head_snapshot();
2088+
if head.beacon_block.slot() != request_slot {
2089+
return Err(Error::NoBlockForSlot(request_slot));
2090+
}
2091+
2092+
let beacon_block_root = head.beacon_block_root;
2093+
2094+
// TODO(EIP-7732): Check if we've seen a SignedExecutionPayloadEnvelope
2095+
// referencing this block root. For now, default to false.
2096+
let payload_present = false;
2097+
2098+
// TODO(EIP-7732): Check blob data availability. For now, default to false.
2099+
let blob_data_available = false;
2100+
2101+
Ok(PayloadAttestationData {
2102+
beacon_block_root,
2103+
slot: head.beacon_block.slot(),
2104+
payload_present,
2105+
blob_data_available,
2106+
})
2107+
}
2108+
20702109
/// Performs the same validation as `Self::verify_unaggregated_attestation_for_gossip`, but for
20712110
/// multiple attestations using batch BLS verification. Batch verification can provide
20722111
/// significant CPU-time savings compared to individual verification.

beacon_node/beacon_chain/src/errors.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub enum BeaconChainError {
5353
},
5454
SlotClockDidNotStart,
5555
NoStateForSlot(Slot),
56+
NoBlockForSlot(Slot),
5657
BeaconStateError(BeaconStateError),
5758
EpochCacheError(EpochCacheError),
5859
DBInconsistent(String),

beacon_node/beacon_chain/src/metrics.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,17 @@ pub static ATTESTATION_PRODUCTION_CACHE_PRIME_SECONDS: LazyLock<Result<Histogram
497497
)
498498
});
499499

500+
/*
501+
* Payload Attestation Production
502+
*/
503+
pub static PAYLOAD_ATTESTATION_PRODUCTION_SECONDS: LazyLock<Result<Histogram>> =
504+
LazyLock::new(|| {
505+
try_create_histogram(
506+
"beacon_payload_attestation_production_seconds",
507+
"Full runtime of payload attestation production",
508+
)
509+
});
510+
500511
/*
501512
* Fork Choice
502513
*/

beacon_node/http_api/src/lib.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2456,6 +2456,46 @@ pub fn serve<T: BeaconChainTypes>(
24562456
task_spawner_filter.clone(),
24572457
);
24582458

2459+
// GET validator/payload_attestation_data/{slot}
2460+
let get_validator_payload_attestation_data = eth_v1
2461+
.clone()
2462+
.and(warp::path("validator"))
2463+
.and(warp::path("payload_attestation_data"))
2464+
.and(warp::path::param::<Slot>().or_else(|_| async {
2465+
Err(warp_utils::reject::custom_bad_request(
2466+
"Invalid slot".to_string(),
2467+
))
2468+
}))
2469+
.and(warp::path::end())
2470+
.and(not_while_syncing_filter.clone())
2471+
.and(task_spawner_filter.clone())
2472+
.and(chain_filter.clone())
2473+
.then(
2474+
|slot: Slot,
2475+
not_synced_filter: Result<(), Rejection>,
2476+
task_spawner: TaskSpawner<T::EthSpec>,
2477+
chain: Arc<BeaconChain<T>>| {
2478+
task_spawner.blocking_response_task(Priority::P0, move || {
2479+
not_synced_filter?;
2480+
2481+
let fork_name = chain.spec.fork_name_at_slot::<T::EthSpec>(slot);
2482+
2483+
let payload_attestation_data = chain
2484+
.produce_payload_attestation_data(slot)
2485+
.map_err(warp_utils::reject::unhandled_error)?;
2486+
2487+
Ok(add_consensus_version_header(
2488+
warp::reply::json(&beacon_response(
2489+
ResponseIncludesVersion::Yes(fork_name),
2490+
payload_attestation_data,
2491+
))
2492+
.into_response(),
2493+
fork_name,
2494+
))
2495+
})
2496+
},
2497+
);
2498+
24592499
// GET validator/aggregate_attestation?attestation_data_root,slot
24602500
let get_validator_aggregate_attestation = get_validator_aggregate_attestation(
24612501
any_version.clone().clone(),
@@ -3307,6 +3347,7 @@ pub fn serve<T: BeaconChainTypes>(
33073347
.uor(get_validator_blocks)
33083348
.uor(get_validator_blinded_blocks)
33093349
.uor(get_validator_attestation_data)
3350+
.uor(get_validator_payload_attestation_data)
33103351
.uor(get_validator_aggregate_attestation)
33113352
.uor(get_validator_sync_committee_contribution)
33123353
.uor(get_lighthouse_health)

beacon_node/http_api/tests/tests.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4072,6 +4072,54 @@ impl ApiTester {
40724072
self
40734073
}
40744074

4075+
pub async fn test_get_validator_payload_attestation_data(self) -> Self {
4076+
let slot = self.chain.slot().unwrap();
4077+
let fork_name = self.chain.spec.fork_name_at_slot::<E>(slot);
4078+
4079+
// Payload attestation data is only available in Gloas fork.
4080+
if !fork_name.gloas_enabled() {
4081+
return self;
4082+
}
4083+
4084+
let result = self
4085+
.client
4086+
.get_validator_payload_attestation_data(slot)
4087+
.await
4088+
.unwrap()
4089+
.into_data();
4090+
4091+
let expected = self.chain.produce_payload_attestation_data(slot).unwrap();
4092+
4093+
assert_eq!(result.beacon_block_root, expected.beacon_block_root);
4094+
assert_eq!(result.slot, expected.slot);
4095+
assert_eq!(result.payload_present, expected.payload_present);
4096+
assert_eq!(result.blob_data_available, expected.blob_data_available);
4097+
4098+
self
4099+
}
4100+
4101+
pub async fn test_get_validator_payload_attestation_data_pre_gloas(self) -> Self {
4102+
let slot = self.chain.slot().unwrap();
4103+
let fork_name = self.chain.spec.fork_name_at_slot::<E>(slot);
4104+
4105+
// This test is for pre-Gloas forks
4106+
if fork_name.gloas_enabled() {
4107+
return self;
4108+
}
4109+
4110+
// The endpoint should return a 400 error for pre-Gloas forks
4111+
match self
4112+
.client
4113+
.get_validator_payload_attestation_data(slot)
4114+
.await
4115+
{
4116+
Ok(result) => panic!("query for pre-Gloas slot should fail, got: {result:?}"),
4117+
Err(e) => assert_eq!(e.status().unwrap(), 400),
4118+
}
4119+
4120+
self
4121+
}
4122+
40754123
#[allow(clippy::await_holding_lock)] // This is a test, so it should be fine.
40764124
pub async fn test_get_validator_aggregate_attestation_v1(self) -> Self {
40774125
let attestation = self
@@ -7552,6 +7600,27 @@ async fn get_validator_attestation_data_with_skip_slots() {
75527600
.await;
75537601
}
75547602

7603+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
7604+
async fn get_validator_payload_attestation_data() {
7605+
// TODO(EIP-7732): Remove this conditional once gloas block production is implemented
7606+
if fork_name_from_env().map_or(false, |f| f.gloas_enabled()) {
7607+
return;
7608+
}
7609+
7610+
ApiTester::new_with_hard_forks()
7611+
.await
7612+
.test_get_validator_payload_attestation_data()
7613+
.await;
7614+
}
7615+
7616+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
7617+
async fn get_validator_payload_attestation_data_pre_gloas() {
7618+
ApiTester::new()
7619+
.await
7620+
.test_get_validator_payload_attestation_data_pre_gloas()
7621+
.await;
7622+
}
7623+
75557624
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
75567625
async fn get_validator_aggregate_attestation_v1() {
75577626
ApiTester::new()

common/eth2/src/lib.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ use ssz::Encode;
4646
use std::fmt;
4747
use std::future::Future;
4848
use std::time::Duration;
49+
use types::PayloadAttestationData;
4950

5051
pub const V1: EndpointVersion = EndpointVersion(1);
5152
pub const V2: EndpointVersion = EndpointVersion(2);
@@ -76,6 +77,7 @@ const HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT: u32 = 4;
7677
const HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT: u32 = 4;
7778
const HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT: u32 = 4;
7879
const HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT: u32 = 4;
80+
const HTTP_PAYLOAD_ATTESTATION_TIMEOUT_QUOTIENT: u32 = 4;
7981
const HTTP_DEFAULT_TIMEOUT_QUOTIENT: u32 = 4;
8082

8183
/// A struct to define a variety of different timeouts for different validator tasks to ensure
@@ -96,6 +98,7 @@ pub struct Timeouts {
9698
pub get_debug_beacon_states: Duration,
9799
pub get_deposit_snapshot: Duration,
98100
pub get_validator_block: Duration,
101+
pub payload_attestation: Duration,
99102
pub default: Duration,
100103
}
101104

@@ -116,6 +119,7 @@ impl Timeouts {
116119
get_debug_beacon_states: timeout,
117120
get_deposit_snapshot: timeout,
118121
get_validator_block: timeout,
122+
payload_attestation: timeout,
119123
default: timeout,
120124
}
121125
}
@@ -138,6 +142,7 @@ impl Timeouts {
138142
get_debug_beacon_states: base_timeout / HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT,
139143
get_deposit_snapshot: base_timeout / HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT,
140144
get_validator_block: base_timeout / HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT,
145+
payload_attestation: base_timeout / HTTP_PAYLOAD_ATTESTATION_TIMEOUT_QUOTIENT,
141146
default: base_timeout / HTTP_DEFAULT_TIMEOUT_QUOTIENT,
142147
}
143148
}
@@ -2555,6 +2560,24 @@ impl BeaconNodeHttpClient {
25552560
self.get_with_timeout(path, self.timeouts.attestation).await
25562561
}
25572562

2563+
/// `GET validator/payload_attestation_data/{slot}`
2564+
pub async fn get_validator_payload_attestation_data(
2565+
&self,
2566+
slot: Slot,
2567+
) -> Result<BeaconResponse<PayloadAttestationData>, Error> {
2568+
let mut path = self.eth_path(V1)?;
2569+
2570+
path.path_segments_mut()
2571+
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
2572+
.push("validator")
2573+
.push("payload_attestation_data")
2574+
.push(&slot.to_string());
2575+
2576+
self.get_with_timeout(path, self.timeouts.payload_attestation)
2577+
.await
2578+
.map(BeaconResponse::ForkVersioned)
2579+
}
2580+
25582581
/// `GET v1/validator/aggregate_attestation?slot,attestation_data_root`
25592582
pub async fn get_validator_aggregate_attestation_v1<E: EthSpec>(
25602583
&self,

0 commit comments

Comments
 (0)