Skip to content

Commit 74c8815

Browse files
committed
add get_payload_attestation_endpoint
1 parent 4b56d03 commit 74c8815

File tree

6 files changed

+184
-1
lines changed

6 files changed

+184
-1
lines changed

beacon_node/beacon_chain/src/beacon_chain.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2064,6 +2064,45 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
20642064
)?)
20652065
}
20662066

2067+
/// Produce a `PayloadAttestationData` for a PTC validator to sign.
2068+
///
2069+
/// This is used by PTC (Payload Timeliness Committee) validators to attest to the
2070+
/// presence/absence of an execution payload and blobs for a given slot.
2071+
pub fn produce_payload_attestation_data(
2072+
&self,
2073+
request_slot: Slot,
2074+
) -> Result<PayloadAttestationData, Error> {
2075+
let _timer = metrics::start_timer(&metrics::PAYLOAD_ATTESTATION_PRODUCTION_SECONDS);
2076+
2077+
// Payload attestations are only valid for the current slot
2078+
let current_slot = self.slot()?;
2079+
if request_slot != current_slot {
2080+
return Err(Error::InvalidSlot(request_slot));
2081+
}
2082+
2083+
// Check if we've seen a block for this slot from the canonical head
2084+
let head = self.head_snapshot();
2085+
if head.beacon_block.slot() != request_slot {
2086+
return Err(Error::NoBlockForSlot(request_slot));
2087+
}
2088+
2089+
let beacon_block_root = head.beacon_block_root;
2090+
2091+
// TODO(EIP-7732): Check if we've seen a SignedExecutionPayloadEnvelope
2092+
// referencing this block root. For now, default to false.
2093+
let payload_present = false;
2094+
2095+
// TODO(EIP-7732): Check blob data availability. For now, default to false.
2096+
let blob_data_available = false;
2097+
2098+
Ok(PayloadAttestationData {
2099+
beacon_block_root,
2100+
slot: head.beacon_block.slot(),
2101+
payload_present,
2102+
blob_data_available,
2103+
})
2104+
}
2105+
20672106
/// Performs the same validation as `Self::verify_unaggregated_attestation_for_gossip`, but for
20682107
/// multiple attestations using batch BLS verification. Batch verification can provide
20692108
/// 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
@@ -52,6 +52,7 @@ pub enum BeaconChainError {
5252
},
5353
SlotClockDidNotStart,
5454
NoStateForSlot(Slot),
55+
NoBlockForSlot(Slot),
5556
BeaconStateError(BeaconStateError),
5657
EpochCacheError(EpochCacheError),
5758
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: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3517,6 +3517,45 @@ pub fn serve<T: BeaconChainTypes>(
35173517
},
35183518
);
35193519

3520+
// GET validator/payload_attestation_data/{slot}
3521+
let get_validator_payload_attestation_data = eth_v1
3522+
.and(warp::path("validator"))
3523+
.and(warp::path("payload_attestation_data"))
3524+
.and(warp::path::param::<Slot>().or_else(|_| async {
3525+
Err(warp_utils::reject::custom_bad_request(
3526+
"Invalid slot".to_string(),
3527+
))
3528+
}))
3529+
.and(warp::path::end())
3530+
.and(not_while_syncing_filter.clone())
3531+
.and(task_spawner_filter.clone())
3532+
.and(chain_filter.clone())
3533+
.then(
3534+
|slot: Slot,
3535+
not_synced_filter: Result<(), Rejection>,
3536+
task_spawner: TaskSpawner<T::EthSpec>,
3537+
chain: Arc<BeaconChain<T>>| {
3538+
task_spawner.blocking_response_task(Priority::P0, move || {
3539+
not_synced_filter?;
3540+
3541+
let fork_name = chain.spec.fork_name_at_slot::<T::EthSpec>(slot);
3542+
3543+
let payload_attestation_data = chain
3544+
.produce_payload_attestation_data(slot)
3545+
.map_err(warp_utils::reject::unhandled_error)?;
3546+
3547+
Ok(add_consensus_version_header(
3548+
warp::reply::json(&beacon_response(
3549+
ResponseIncludesVersion::Yes(fork_name),
3550+
payload_attestation_data,
3551+
))
3552+
.into_response(),
3553+
fork_name,
3554+
))
3555+
})
3556+
},
3557+
);
3558+
35203559
// GET validator/aggregate_attestation?attestation_data_root,slot
35213560
let get_validator_aggregate_attestation = any_version
35223561
.and(warp::path("validator"))
@@ -4948,6 +4987,7 @@ pub fn serve<T: BeaconChainTypes>(
49484987
.uor(get_validator_blocks)
49494988
.uor(get_validator_blinded_blocks)
49504989
.uor(get_validator_attestation_data)
4990+
.uor(get_validator_payload_attestation_data)
49514991
.uor(get_validator_aggregate_attestation)
49524992
.uor(get_validator_sync_committee_contribution)
49534993
.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
@@ -4060,6 +4060,54 @@ impl ApiTester {
40604060
self
40614061
}
40624062

4063+
pub async fn test_get_validator_payload_attestation_data(self) -> Self {
4064+
let slot = self.chain.slot().unwrap();
4065+
let fork_name = self.chain.spec.fork_name_at_slot::<E>(slot);
4066+
4067+
// Payload attestation data is only available in Gloas fork.
4068+
if !fork_name.gloas_enabled() {
4069+
return self;
4070+
}
4071+
4072+
let result = self
4073+
.client
4074+
.get_validator_payload_attestation_data(slot)
4075+
.await
4076+
.unwrap()
4077+
.into_data();
4078+
4079+
let expected = self.chain.produce_payload_attestation_data(slot).unwrap();
4080+
4081+
assert_eq!(result.beacon_block_root, expected.beacon_block_root);
4082+
assert_eq!(result.slot, expected.slot);
4083+
assert_eq!(result.payload_present, expected.payload_present);
4084+
assert_eq!(result.blob_data_available, expected.blob_data_available);
4085+
4086+
self
4087+
}
4088+
4089+
pub async fn test_get_validator_payload_attestation_data_pre_gloas(self) -> Self {
4090+
let slot = self.chain.slot().unwrap();
4091+
let fork_name = self.chain.spec.fork_name_at_slot::<E>(slot);
4092+
4093+
// This test is for pre-Gloas forks
4094+
if fork_name.gloas_enabled() {
4095+
return self;
4096+
}
4097+
4098+
// The endpoint should return a 400 error for pre-Gloas forks
4099+
match self
4100+
.client
4101+
.get_validator_payload_attestation_data(slot)
4102+
.await
4103+
{
4104+
Ok(result) => panic!("query for pre-Gloas slot should fail, got: {result:?}"),
4105+
Err(e) => assert_eq!(e.status().unwrap(), 400),
4106+
}
4107+
4108+
self
4109+
}
4110+
40634111
#[allow(clippy::await_holding_lock)] // This is a test, so it should be fine.
40644112
pub async fn test_get_validator_aggregate_attestation_v1(self) -> Self {
40654113
let attestation = self
@@ -7464,6 +7512,27 @@ async fn get_validator_attestation_data_with_skip_slots() {
74647512
.await;
74657513
}
74667514

7515+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
7516+
async fn get_validator_payload_attestation_data() {
7517+
// TODO(EIP-7732): Remove this conditional once gloas block production is implemented
7518+
if fork_name_from_env().map_or(false, |f| f.gloas_enabled()) {
7519+
return;
7520+
}
7521+
7522+
ApiTester::new_with_hard_forks()
7523+
.await
7524+
.test_get_validator_payload_attestation_data()
7525+
.await;
7526+
}
7527+
7528+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
7529+
async fn get_validator_payload_attestation_data_pre_gloas() {
7530+
ApiTester::new()
7531+
.await
7532+
.test_get_validator_payload_attestation_data_pre_gloas()
7533+
.await;
7534+
}
7535+
74677536
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
74687537
async fn get_validator_aggregate_attestation_v1() {
74697538
ApiTester::new()

common/eth2/src/lib.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ pub mod types;
1818
pub use self::error::{Error, ok_or_error, success_or_error};
1919
use self::mixin::{RequestAccept, ResponseOptional};
2020
use self::types::*;
21-
use ::types::beacon_response::ExecutionOptimisticFinalizedBeaconResponse;
21+
use ::types::PayloadAttestationData;
22+
use ::types::beacon_response::{ExecutionOptimisticFinalizedBeaconResponse, ForkVersionedResponse};
2223
use educe::Educe;
2324
use futures::Stream;
2425
use futures_util::StreamExt;
@@ -66,6 +67,7 @@ const HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT: u32 = 4;
6667
const HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT: u32 = 4;
6768
const HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT: u32 = 4;
6869
const HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT: u32 = 4;
70+
const HTTP_PAYLOAD_ATTESTATION_TIMEOUT_QUOTIENT: u32 = 4;
6971
const HTTP_DEFAULT_TIMEOUT_QUOTIENT: u32 = 4;
7072

7173
/// A struct to define a variety of different timeouts for different validator tasks to ensure
@@ -86,6 +88,7 @@ pub struct Timeouts {
8688
pub get_debug_beacon_states: Duration,
8789
pub get_deposit_snapshot: Duration,
8890
pub get_validator_block: Duration,
91+
pub payload_attestation: Duration,
8992
pub default: Duration,
9093
}
9194

@@ -106,6 +109,7 @@ impl Timeouts {
106109
get_debug_beacon_states: timeout,
107110
get_deposit_snapshot: timeout,
108111
get_validator_block: timeout,
112+
payload_attestation: timeout,
109113
default: timeout,
110114
}
111115
}
@@ -128,6 +132,7 @@ impl Timeouts {
128132
get_debug_beacon_states: base_timeout / HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT,
129133
get_deposit_snapshot: base_timeout / HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT,
130134
get_validator_block: base_timeout / HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT,
135+
payload_attestation: base_timeout / HTTP_PAYLOAD_ATTESTATION_TIMEOUT_QUOTIENT,
131136
default: base_timeout / HTTP_DEFAULT_TIMEOUT_QUOTIENT,
132137
}
133138
}
@@ -2528,6 +2533,24 @@ impl BeaconNodeHttpClient {
25282533
self.get_with_timeout(path, self.timeouts.attestation).await
25292534
}
25302535

2536+
/// `GET validator/payload_attestation_data/{slot}`
2537+
pub async fn get_validator_payload_attestation_data(
2538+
&self,
2539+
slot: Slot,
2540+
) -> Result<BeaconResponse<PayloadAttestationData>, Error> {
2541+
let mut path = self.eth_path(V1)?;
2542+
2543+
path.path_segments_mut()
2544+
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
2545+
.push("validator")
2546+
.push("payload_attestation_data")
2547+
.push(&slot.to_string());
2548+
2549+
self.get_with_timeout(path, self.timeouts.payload_attestation)
2550+
.await
2551+
.map(BeaconResponse::ForkVersioned)
2552+
}
2553+
25312554
/// `GET v1/validator/aggregate_attestation?slot,attestation_data_root`
25322555
pub async fn get_validator_aggregate_attestation_v1<E: EthSpec>(
25332556
&self,

0 commit comments

Comments
 (0)