Skip to content

Commit 4da2eb8

Browse files
committed
add get_payload_attestation_endpoint
1 parent 77e1c67 commit 4da2eb8

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
@@ -3491,6 +3491,45 @@ pub fn serve<T: BeaconChainTypes>(
34913491
},
34923492
);
34933493

3494+
// GET validator/payload_attestation_data/{slot}
3495+
let get_validator_payload_attestation_data = eth_v1
3496+
.and(warp::path("validator"))
3497+
.and(warp::path("payload_attestation_data"))
3498+
.and(warp::path::param::<Slot>().or_else(|_| async {
3499+
Err(warp_utils::reject::custom_bad_request(
3500+
"Invalid slot".to_string(),
3501+
))
3502+
}))
3503+
.and(warp::path::end())
3504+
.and(not_while_syncing_filter.clone())
3505+
.and(task_spawner_filter.clone())
3506+
.and(chain_filter.clone())
3507+
.then(
3508+
|slot: Slot,
3509+
not_synced_filter: Result<(), Rejection>,
3510+
task_spawner: TaskSpawner<T::EthSpec>,
3511+
chain: Arc<BeaconChain<T>>| {
3512+
task_spawner.blocking_response_task(Priority::P0, move || {
3513+
not_synced_filter?;
3514+
3515+
let fork_name = chain.spec.fork_name_at_slot::<T::EthSpec>(slot);
3516+
3517+
let payload_attestation_data = chain
3518+
.produce_payload_attestation_data(slot)
3519+
.map_err(warp_utils::reject::unhandled_error)?;
3520+
3521+
Ok(add_consensus_version_header(
3522+
warp::reply::json(&beacon_response(
3523+
ResponseIncludesVersion::Yes(fork_name),
3524+
payload_attestation_data,
3525+
))
3526+
.into_response(),
3527+
fork_name,
3528+
))
3529+
})
3530+
},
3531+
);
3532+
34943533
// GET validator/aggregate_attestation?attestation_data_root,slot
34953534
let get_validator_aggregate_attestation = any_version
34963535
.and(warp::path("validator"))
@@ -4922,6 +4961,7 @@ pub fn serve<T: BeaconChainTypes>(
49224961
.uor(get_validator_blocks)
49234962
.uor(get_validator_blinded_blocks)
49244963
.uor(get_validator_attestation_data)
4964+
.uor(get_validator_payload_attestation_data)
49254965
.uor(get_validator_aggregate_attestation)
49264966
.uor(get_validator_sync_committee_contribution)
49274967
.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
@@ -4034,6 +4034,54 @@ impl ApiTester {
40344034
self
40354035
}
40364036

4037+
pub async fn test_get_validator_payload_attestation_data(self) -> Self {
4038+
let slot = self.chain.slot().unwrap();
4039+
let fork_name = self.chain.spec.fork_name_at_slot::<E>(slot);
4040+
4041+
// Payload attestation data is only available in Gloas fork.
4042+
if !fork_name.gloas_enabled() {
4043+
return self;
4044+
}
4045+
4046+
let result = self
4047+
.client
4048+
.get_validator_payload_attestation_data(slot)
4049+
.await
4050+
.unwrap()
4051+
.into_data();
4052+
4053+
let expected = self.chain.produce_payload_attestation_data(slot).unwrap();
4054+
4055+
assert_eq!(result.beacon_block_root, expected.beacon_block_root);
4056+
assert_eq!(result.slot, expected.slot);
4057+
assert_eq!(result.payload_present, expected.payload_present);
4058+
assert_eq!(result.blob_data_available, expected.blob_data_available);
4059+
4060+
self
4061+
}
4062+
4063+
pub async fn test_get_validator_payload_attestation_data_pre_gloas(self) -> Self {
4064+
let slot = self.chain.slot().unwrap();
4065+
let fork_name = self.chain.spec.fork_name_at_slot::<E>(slot);
4066+
4067+
// This test is for pre-Gloas forks
4068+
if fork_name.gloas_enabled() {
4069+
return self;
4070+
}
4071+
4072+
// The endpoint should return a 400 error for pre-Gloas forks
4073+
match self
4074+
.client
4075+
.get_validator_payload_attestation_data(slot)
4076+
.await
4077+
{
4078+
Ok(result) => panic!("query for pre-Gloas slot should fail, got: {result:?}"),
4079+
Err(e) => assert_eq!(e.status().unwrap(), 400),
4080+
}
4081+
4082+
self
4083+
}
4084+
40374085
#[allow(clippy::await_holding_lock)] // This is a test, so it should be fine.
40384086
pub async fn test_get_validator_aggregate_attestation_v1(self) -> Self {
40394087
let attestation = self
@@ -7438,6 +7486,27 @@ async fn get_validator_attestation_data_with_skip_slots() {
74387486
.await;
74397487
}
74407488

7489+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
7490+
async fn get_validator_payload_attestation_data() {
7491+
// TODO(EIP-7732): Remove this conditional once gloas block production is implemented
7492+
if fork_name_from_env().map_or(false, |f| f.gloas_enabled()) {
7493+
return;
7494+
}
7495+
7496+
ApiTester::new_with_hard_forks()
7497+
.await
7498+
.test_get_validator_payload_attestation_data()
7499+
.await;
7500+
}
7501+
7502+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
7503+
async fn get_validator_payload_attestation_data_pre_gloas() {
7504+
ApiTester::new()
7505+
.await
7506+
.test_get_validator_payload_attestation_data_pre_gloas()
7507+
.await;
7508+
}
7509+
74417510
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
74427511
async fn get_validator_aggregate_attestation_v1() {
74437512
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)