anvil/server/
beacon_handler.rs

1use crate::eth::{
2    EthApi,
3    beacon::{BeaconError, BeaconResponse},
4};
5use alloy_eips::BlockId;
6use alloy_primitives::B256;
7use alloy_rpc_types_beacon::{
8    header::Header,
9    sidecar::{BlobData, GetBlobsResponse},
10};
11use axum::{
12    Json,
13    extract::{Path, Query, State},
14    response::{IntoResponse, Response},
15};
16use hyper::StatusCode;
17use std::{collections::HashMap, str::FromStr as _};
18
19/// Handles incoming Beacon API requests for blob sidecars
20///
21/// GET /eth/v1/beacon/blob_sidecars/{block_id}
22pub async fn handle_get_blob_sidecars(
23    State(api): State<EthApi>,
24    Path(block_id): Path<String>,
25    Query(params): Query<HashMap<String, String>>,
26) -> Response {
27    // Parse block_id from path parameter
28    let Ok(block_id) = BlockId::from_str(&block_id) else {
29        return BeaconError::invalid_block_id(block_id).into_response();
30    };
31
32    // Parse indices from query parameters
33    // Supports both comma-separated (?indices=1,2,3) and repeated parameters (?indices=1&indices=2)
34    let indices: Vec<u64> = params
35        .get("indices")
36        .map(|s| s.split(',').filter_map(|idx| idx.trim().parse::<u64>().ok()).collect())
37        .unwrap_or_default();
38
39    // Get the blob sidecars using existing EthApi logic
40    match api.anvil_get_blob_sidecars_by_block_id(block_id) {
41        Ok(Some(sidecar)) => BeaconResponse::with_flags(
42            sidecar
43                .into_iter()
44                .filter(|blob_item| indices.is_empty() || indices.contains(&blob_item.index))
45                .map(|blob_item| BlobData {
46                    index: blob_item.index,
47                    blob: blob_item.blob,
48                    kzg_commitment: blob_item.kzg_commitment,
49                    kzg_proof: blob_item.kzg_proof,
50                    signed_block_header: Header::default(), // Not available in Anvil
51                    kzg_commitment_inclusion_proof: vec![], // Not available in Anvil
52                })
53                .collect::<Vec<_>>(),
54            false, // Not available in Anvil
55            false, // Not available in Anvil
56        )
57        .into_response(),
58        Ok(None) => BeaconError::block_not_found().into_response(),
59        Err(_) => BeaconError::internal_error().into_response(),
60    }
61}
62
63/// Handles incoming Beacon API requests for blobs
64///
65/// GET /eth/v1/beacon/blobs/{block_id}
66pub async fn handle_get_blobs(
67    State(api): State<EthApi>,
68    Path(block_id): Path<String>,
69    Query(versioned_hashes): Query<HashMap<String, String>>,
70) -> Response {
71    // Parse block_id from path parameter
72    let Ok(block_id) = BlockId::from_str(&block_id) else {
73        return BeaconError::invalid_block_id(block_id).into_response();
74    };
75
76    // Parse indices from query parameters
77    // Supports both comma-separated (?indices=1,2,3) and repeated parameters (?indices=1&indices=2)
78    let versioned_hashes: Vec<B256> = versioned_hashes
79        .get("versioned_hashes")
80        .map(|s| s.split(',').filter_map(|hash| B256::from_str(hash.trim()).ok()).collect())
81        .unwrap_or_default();
82
83    // Get the blob sidecars using existing EthApi logic
84    match api.anvil_get_blobs_by_block_id(block_id, versioned_hashes) {
85        Ok(Some(blobs)) => (
86            StatusCode::OK,
87            Json(GetBlobsResponse { execution_optimistic: false, finalized: false, data: blobs }),
88        )
89            .into_response(),
90        Ok(None) => BeaconError::block_not_found().into_response(),
91        Err(_) => BeaconError::internal_error().into_response(),
92    }
93}