Skip to content

Commit 2d7698d

Browse files
committed
Add Pectra related API endpoints:
- Add `GET /eth/v2/validator/aggregate_attestation` - Add `GET /eth/v2/beacon/blocks/{block_id}/attestations` - Add `GET /eth/v2/beacon/pool/attestations` - Add `GET /eth/v2/beacon/pool/attester_slashings` - Add `POST /eth/v2/beacon/pool/attester_slashings` - Add `POST /eth/v1/beacon/states/{state_id}/validator_identities` - Add `POST /eth/v2/validator/aggregate_and_proofs`
1 parent dbae6e1 commit 2d7698d

File tree

8 files changed

+388
-57
lines changed

8 files changed

+388
-57
lines changed

grandine-snapshot-tests

http_api/src/error.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use bls::SignatureBytes;
1313
use futures::channel::oneshot::Canceled;
1414
use http_api_utils::ApiError;
1515
use serde::{Serialize, Serializer};
16+
use ssz::H256;
1617
use thiserror::Error;
1718
use tokio::task::JoinError;
1819
use types::{deneb::primitives::BlobIndex, phase0::primitives::Slot};
@@ -21,6 +22,8 @@ use types::{deneb::primitives::BlobIndex, phase0::primitives::Slot};
2122
pub enum Error {
2223
#[error("attestation cannot be found")]
2324
AttestationNotFound,
25+
#[error("block {block_root} not validated")]
26+
BlockNotValidatedForAggregation { block_root: H256 },
2427
#[error("block not found")]
2528
BlockNotFound,
2629
#[error(transparent)]
@@ -225,9 +228,10 @@ impl Error {
225228
| Self::UnableToProduceBeaconBlock
226229
| Self::UnableToProduceBlindedBlock => StatusCode::INTERNAL_SERVER_ERROR,
227230
Self::EndpointNotImplemented => StatusCode::NOT_IMPLEMENTED,
228-
Self::HeadFarBehind { .. } | Self::HeadIsOptimistic | Self::NodeIsSyncing => {
229-
StatusCode::SERVICE_UNAVAILABLE
230-
}
231+
Self::BlockNotValidatedForAggregation { .. }
232+
| Self::HeadFarBehind { .. }
233+
| Self::HeadIsOptimistic
234+
| Self::NodeIsSyncing => StatusCode::SERVICE_UNAVAILABLE,
231235
}
232236
}
233237

http_api/src/extractors.rs

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ use types::{
3131
config::Config,
3232
nonstandard::Phase,
3333
phase0::{
34-
containers::{ProposerSlashing, SignedVoluntaryExit},
34+
containers::{
35+
AttesterSlashing as Phase0AttesterSlashing, ProposerSlashing, SignedVoluntaryExit,
36+
},
3537
primitives::{Epoch, ValidatorIndex},
3638
},
3739
preset::Preset,
@@ -196,7 +198,7 @@ impl<S> FromRequest<S, Body> for EthJson<Box<SignedVoluntaryExit>> {
196198
}
197199

198200
#[async_trait]
199-
impl<S, P: Preset> FromRequest<S, Body> for EthJson<Box<AttesterSlashing<P>>> {
201+
impl<S, P: Preset> FromRequest<S, Body> for EthJson<Box<Phase0AttesterSlashing<P>>> {
200202
type Rejection = Error;
201203

202204
async fn from_request(request: Request<Body>, _state: &S) -> Result<Self, Self::Rejection> {
@@ -208,6 +210,27 @@ impl<S, P: Preset> FromRequest<S, Body> for EthJson<Box<AttesterSlashing<P>>> {
208210
}
209211
}
210212

213+
#[async_trait]
214+
impl<S, P: Preset> FromRequest<S, Body> for EthJson<Box<AttesterSlashing<P>>> {
215+
type Rejection = Error;
216+
217+
async fn from_request(request: Request<Body>, _state: &S) -> Result<Self, Self::Rejection> {
218+
match extract_phase(&request)? {
219+
Phase::Phase0 | Phase::Altair | Phase::Bellatrix | Phase::Capella | Phase::Deneb => {
220+
request
221+
.extract()
222+
.await
223+
.map(|Json(slashing)| Self(Box::new(AttesterSlashing::Phase0(slashing))))
224+
}
225+
Phase::Electra => request
226+
.extract()
227+
.await
228+
.map(|Json(slashing)| Self(Box::new(AttesterSlashing::Electra(slashing)))),
229+
}
230+
.map_err(Error::InvalidJsonBody)
231+
}
232+
}
233+
211234
#[async_trait]
212235
impl<S, P: Preset> FromRequest<S, Body> for EthJson<Vec<Arc<Attestation<P>>>> {
213236
type Rejection = Error;
@@ -371,15 +394,7 @@ where
371394
request.extract_parts::<TypedHeader<ContentType>>().await?;
372395

373396
if content_type == ContentType::octet_stream() {
374-
let phase = request
375-
.headers()
376-
.get(ETH_CONSENSUS_VERSION)
377-
.ok_or(Error::MissingEthConsensusVersionHeader)?
378-
.to_str()?
379-
.parse()
380-
.map_err(AnyhowError::msg)
381-
.map_err(Error::InvalidEthConsensusVersionHeader)?;
382-
397+
let phase = extract_phase(&request)?;
383398
let bytes = Bytes::from_request(request, state).await?;
384399
let block = T::from_ssz(&phase, bytes)?;
385400
return Ok(Self(block));
@@ -393,3 +408,16 @@ where
393408
run.await.map_err(Error::InvalidBlock)
394409
}
395410
}
411+
412+
fn extract_phase(request: &Request<Body>) -> Result<Phase, Error> {
413+
request
414+
.headers()
415+
.get(ETH_CONSENSUS_VERSION)
416+
.ok_or(Error::MissingEthConsensusVersionHeader)?
417+
.to_str()
418+
.map_err(AnyhowError::msg)
419+
.map_err(Error::InvalidEthConsensusVersionHeader)?
420+
.parse()
421+
.map_err(AnyhowError::msg)
422+
.map_err(Error::InvalidEthConsensusVersionHeader)
423+
}

http_api/src/routing.rs

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,22 @@ use crate::{
2929
misc::SyncedStatus,
3030
standard::{
3131
beacon_events, beacon_heads, beacon_state, blob_sidecars, block, block_attestations,
32-
block_headers, block_id_headers, block_rewards, block_root, config_spec, debug_fork_choice,
33-
deposit_contract, expected_withdrawals, fork_schedule, genesis, get_state_validators,
34-
node_health, node_identity, node_peer, node_peer_count, node_peers, node_syncing_status,
35-
node_version, pool_attestations, pool_attester_slashings, pool_bls_to_execution_changes,
32+
block_attestations_v2, block_headers, block_id_headers, block_rewards, block_root,
33+
config_spec, debug_fork_choice, deposit_contract, expected_withdrawals, fork_schedule,
34+
genesis, get_state_validators, node_health, node_identity, node_peer, node_peer_count,
35+
node_peers, node_syncing_status, node_version, pool_attestations, pool_attestations_v2,
36+
pool_attester_slashings, pool_attester_slashings_v2, pool_bls_to_execution_changes,
3637
pool_proposer_slashings, pool_voluntary_exits, post_state_validators,
3738
publish_blinded_block, publish_blinded_block_v2, publish_block, publish_block_v2,
3839
state_committees, state_finality_checkpoints, state_fork, state_randao, state_root,
39-
state_sync_committees, state_validator, state_validator_balances, submit_pool_attestations,
40-
submit_pool_attester_slashing, submit_pool_bls_to_execution_change,
40+
state_sync_committees, state_validator, state_validator_balances,
41+
state_validator_identities, submit_pool_attestations, submit_pool_attester_slashing,
42+
submit_pool_attester_slashing_v2, submit_pool_bls_to_execution_change,
4143
submit_pool_proposer_slashing, submit_pool_sync_committees, submit_pool_voluntary_exit,
42-
sync_committee_rewards, validator_aggregate_attestation, validator_attestation_data,
43-
validator_attester_duties, validator_beacon_committee_selections, validator_blinded_block,
44-
validator_block, validator_block_v3, validator_liveness, validator_prepare_beacon_proposer,
44+
sync_committee_rewards, validator_aggregate_attestation,
45+
validator_aggregate_attestation_v2, validator_attestation_data, validator_attester_duties,
46+
validator_beacon_committee_selections, validator_blinded_block, validator_block,
47+
validator_block_v3, validator_liveness, validator_prepare_beacon_proposer,
4548
validator_proposer_duties, validator_publish_aggregate_and_proofs,
4649
validator_publish_contributions_and_proofs, validator_register_validator,
4750
validator_subscribe_to_beacon_committee, validator_subscribe_to_sync_committees,
@@ -307,6 +310,7 @@ fn gui_routes<P: Preset, W: Wait>() -> Router<NormalState<P, W>> {
307310
// (`beacon`, `config`, `debug`, etc.). The same could be done with `gui`, but
308311
// `PATCH /features` requires special attention because it's more dangerous.
309312

313+
#[expect(clippy::too_many_lines)]
310314
fn eth_v1_beacon_routes<P: Preset, W: Wait>(state: NormalState<P, W>) -> Router<NormalState<P, W>> {
311315
let state_routes = Router::new()
312316
.route("/eth/v1/beacon/states/:state_id/root", get(state_root))
@@ -323,6 +327,10 @@ fn eth_v1_beacon_routes<P: Preset, W: Wait>(state: NormalState<P, W>) -> Router<
323327
"/eth/v1/beacon/states/:state_id/validators",
324328
post(post_state_validators),
325329
)
330+
.route(
331+
"/eth/v1/beacon/states/:state_id/validator_identities",
332+
post(state_validator_identities),
333+
)
326334
.route(
327335
"/eth/v1/beacon/states/:state_id/validators/:validator_id",
328336
get(state_validator),
@@ -345,7 +353,7 @@ fn eth_v1_beacon_routes<P: Preset, W: Wait>(state: NormalState<P, W>) -> Router<
345353
.route("/eth/v1/beacon/headers", get(block_headers))
346354
.route("/eth/v1/beacon/headers/:block_id", get(block_id_headers));
347355

348-
let block_routes = Router::new()
356+
let block_v1_routes = Router::new()
349357
.route("/eth/v1/beacon/blocks/:block_id/root", get(block_root))
350358
.route(
351359
"/eth/v1/beacon/blocks/:block_id/attestations",
@@ -359,7 +367,12 @@ fn eth_v1_beacon_routes<P: Preset, W: Wait>(state: NormalState<P, W>) -> Router<
359367
)),
360368
);
361369

362-
let pool_routes = Router::new()
370+
let block_v2_routes = Router::new().route(
371+
"/eth/v2/beacon/blocks/:block_id/attestations",
372+
get(block_attestations_v2),
373+
);
374+
375+
let pool_v1_routes = Router::new()
363376
.route(
364377
"/eth/v1/beacon/pool/attestations",
365378
get(pool_attestations).post(submit_pool_attestations),
@@ -385,6 +398,16 @@ fn eth_v1_beacon_routes<P: Preset, W: Wait>(state: NormalState<P, W>) -> Router<
385398
post(submit_pool_sync_committees),
386399
);
387400

401+
let pool_v2_routes = Router::new()
402+
.route(
403+
"/eth/v2/beacon/pool/attestations",
404+
get(pool_attestations_v2),
405+
)
406+
.route(
407+
"/eth/v2/beacon/pool/attester_slashings",
408+
get(pool_attester_slashings_v2).post(submit_pool_attester_slashing_v2),
409+
);
410+
388411
let reward_routes = Router::new()
389412
.route(
390413
"/eth/v1/beacon/rewards/blocks/:block_id",
@@ -407,8 +430,10 @@ fn eth_v1_beacon_routes<P: Preset, W: Wait>(state: NormalState<P, W>) -> Router<
407430
.route("/eth/v1/beacon/genesis", get(genesis))
408431
.merge(state_routes)
409432
.merge(header_routes)
410-
.merge(block_routes)
411-
.merge(pool_routes)
433+
.merge(block_v1_routes)
434+
.merge(block_v2_routes)
435+
.merge(pool_v1_routes)
436+
.merge(pool_v2_routes)
412437
.merge(reward_routes)
413438
}
414439

@@ -544,6 +569,14 @@ fn eth_v2_validator_routes<P: Preset, W: Wait>(
544569
state: NormalState<P, W>,
545570
) -> Router<NormalState<P, W>> {
546571
Router::new()
572+
.route(
573+
"/eth/v2/validator/aggregate_attestation",
574+
get(validator_aggregate_attestation_v2),
575+
)
576+
.route(
577+
"/eth/v2/validator/aggregate_and_proofs",
578+
post(validator_publish_aggregate_and_proofs),
579+
)
547580
.route("/eth/v2/validator/blocks/:slot", get(validator_block))
548581
.layer(axum::middleware::map_request_with_state(
549582
state,

0 commit comments

Comments
 (0)