Skip to content

Commit c233c8e

Browse files
authored
Add endpoint to get execution proofs (#10)
1 parent 11578fb commit c233c8e

File tree

7 files changed

+231
-4
lines changed

7 files changed

+231
-4
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

beacon_node/http_api/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ sensitive_url = { workspace = true }
3939
serde = { workspace = true }
4040
serde_json = { workspace = true }
4141
slot_clock = { workspace = true }
42+
ssz_types = { workspace = true }
4243
state_processing = { workspace = true }
4344
store = { workspace = true }
4445
sysinfo = { workspace = true }

beacon_node/http_api/src/block_id.rs

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkip
55
use eth2::beacon_response::{ExecutionOptimisticFinalizedMetadata, UnversionedResponse};
66
use eth2::types::BlockId as CoreBlockId;
77
use eth2::types::DataColumnIndicesQuery;
8-
use eth2::types::{BlobIndicesQuery, BlobWrapper, BlobsVersionedHashesQuery};
8+
use eth2::types::{
9+
BlobIndicesQuery, BlobWrapper, BlobsVersionedHashesQuery, ExecutionProofIdsQuery,
10+
};
911
use fixed_bytes::FixedBytesExtended;
12+
use ssz_types::RuntimeVariableList;
1013
use std::fmt;
1114
use std::str::FromStr;
1215
use std::sync::Arc;
1316
use types::{
14-
BlobSidecarList, DataColumnSidecarList, EthSpec, ForkName, Hash256, SignedBeaconBlock,
15-
SignedBlindedBeaconBlock, Slot,
17+
BlobSidecarList, DataColumnSidecarList, EthSpec, ExecutionProof, ExecutionProofId, ForkName,
18+
Hash256, MAX_PROOFS, SignedBeaconBlock, SignedBlindedBeaconBlock, Slot,
1619
};
1720
use warp::Rejection;
1821

@@ -30,6 +33,12 @@ type DataColumnsResponse<T> = (
3033
Finalized,
3134
);
3235

36+
type ExecutionProofsResponse = (
37+
RuntimeVariableList<Arc<ExecutionProof>>,
38+
ExecutionOptimistic,
39+
Finalized,
40+
);
41+
3342
impl BlockId {
3443
pub fn from_slot(slot: Slot) -> Self {
3544
Self(CoreBlockId::Slot(slot))
@@ -312,6 +321,53 @@ impl BlockId {
312321
))
313322
}
314323

324+
pub fn get_execution_proofs<T: BeaconChainTypes>(
325+
&self,
326+
query: ExecutionProofIdsQuery,
327+
chain: &BeaconChain<T>,
328+
) -> Result<ExecutionProofsResponse, Rejection> {
329+
if !chain.spec.is_zkvm_enabled() {
330+
return Err(warp_utils::reject::custom_bad_request(
331+
"zkvm is not enabled for this node".to_string(),
332+
));
333+
}
334+
335+
let (root, execution_optimistic, finalized) = self.root(chain)?;
336+
let _block = BlockId::blinded_block_by_root(&root, chain)?.ok_or_else(|| {
337+
warp_utils::reject::custom_not_found(format!("beacon block with root {}", root))
338+
})?;
339+
340+
let mut proofs = chain
341+
.store
342+
.get_execution_proofs(&root)
343+
.map_err(warp_utils::reject::unhandled_error)?;
344+
345+
if proofs.is_empty() {
346+
return Err(warp_utils::reject::custom_not_found(format!(
347+
"no execution proofs stored for block {root}"
348+
)));
349+
}
350+
351+
let proof_ids = query
352+
.proof_ids
353+
.map(|ids| {
354+
ids.into_iter()
355+
.map(ExecutionProofId::new)
356+
.collect::<Result<Vec<_>, _>>()
357+
})
358+
.transpose()
359+
.map_err(warp_utils::reject::custom_bad_request)?;
360+
361+
if let Some(proof_ids) = proof_ids {
362+
proofs.retain(|proof| proof_ids.contains(&proof.proof_id));
363+
}
364+
365+
let proof_list = RuntimeVariableList::new(proofs, MAX_PROOFS)
366+
.map_err(|e| warp_utils::reject::custom_server_error(format!("{:?}", e)))?;
367+
368+
Ok((proof_list, execution_optimistic, finalized))
369+
}
370+
315371
#[allow(clippy::type_complexity)]
316372
pub fn get_blinded_block_and_blob_list_filtered<T: BeaconChainTypes>(
317373
&self,

beacon_node/http_api/src/lib.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ pub fn prometheus_metrics() -> warp::filters::log::Log<impl Fn(warp::filters::lo
210210
.or_else(|| starts_with("v2/beacon/blocks"))
211211
.or_else(|| starts_with("v1/beacon/blob_sidecars"))
212212
.or_else(|| starts_with("v1/beacon/blobs"))
213+
.or_else(|| starts_with("v1/beacon/execution_proofs"))
213214
.or_else(|| starts_with("v1/beacon/blocks/head/root"))
214215
.or_else(|| starts_with("v1/beacon/blinded_blocks"))
215216
.or_else(|| starts_with("v2/beacon/blinded_blocks"))
@@ -1394,6 +1395,53 @@ pub fn serve<T: BeaconChainTypes>(
13941395
},
13951396
);
13961397

1398+
// GET beacon/execution_proofs/{block_id}
1399+
let get_execution_proofs = eth_v1
1400+
.clone()
1401+
.and(warp::path("beacon"))
1402+
.and(warp::path("execution_proofs"))
1403+
.and(block_id_or_err)
1404+
.and(warp::path::end())
1405+
.and(multi_key_query::<api_types::ExecutionProofIdsQuery>())
1406+
.and(task_spawner_filter.clone())
1407+
.and(chain_filter.clone())
1408+
.and(warp::header::optional::<api_types::Accept>("accept"))
1409+
.then(
1410+
|block_id: BlockId,
1411+
proof_ids_res: Result<api_types::ExecutionProofIdsQuery, warp::Rejection>,
1412+
task_spawner: TaskSpawner<T::EthSpec>,
1413+
chain: Arc<BeaconChain<T>>,
1414+
accept_header: Option<api_types::Accept>| {
1415+
task_spawner.blocking_response_task(Priority::P1, move || {
1416+
let proof_ids = proof_ids_res?;
1417+
let (proofs, execution_optimistic, finalized) =
1418+
block_id.get_execution_proofs(proof_ids, &chain)?;
1419+
1420+
match accept_header {
1421+
Some(api_types::Accept::Ssz) => Response::builder()
1422+
.status(200)
1423+
.body(proofs.as_ssz_bytes().into())
1424+
.map(|res: Response<Body>| add_ssz_content_type_header(res))
1425+
.map_err(|e| {
1426+
warp_utils::reject::custom_server_error(format!(
1427+
"failed to create response: {}",
1428+
e
1429+
))
1430+
}),
1431+
_ => {
1432+
let res = execution_optimistic_finalized_beacon_response(
1433+
ResponseIncludesVersion::No,
1434+
execution_optimistic,
1435+
finalized,
1436+
&proofs,
1437+
)?;
1438+
Ok(warp::reply::json(&res).into_response())
1439+
}
1440+
}
1441+
})
1442+
},
1443+
);
1444+
13971445
/*
13981446
* beacon/pool
13991447
*/
@@ -3287,6 +3335,7 @@ pub fn serve<T: BeaconChainTypes>(
32873335
.uor(get_beacon_block_root)
32883336
.uor(get_blob_sidecars)
32893337
.uor(get_blobs)
3338+
.uor(get_execution_proofs)
32903339
.uor(get_beacon_pool_attestations)
32913340
.uor(get_beacon_pool_attester_slashings)
32923341
.uor(get_beacon_pool_proposer_slashings)

beacon_node/http_api/tests/tests.rs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ impl ApiTesterConfig {
118118
}
119119

120120
fn with_zkvm(mut self) -> Self {
121+
// TODO(zkproofs): shouldn't need both of these to be enabled
121122
self.enable_zkvm = true;
123+
self.spec.zkvm_enabled = true;
122124
self
123125
}
124126
}
@@ -1962,6 +1964,60 @@ impl ApiTester {
19621964
self
19631965
}
19641966

1967+
pub async fn test_get_execution_proofs(self, filter: bool) -> Self {
1968+
let head = self.chain.head_snapshot();
1969+
let block_root = head.beacon_block_root;
1970+
1971+
let proof_ids = [
1972+
ExecutionProofId::new(0).expect("Valid proof id"),
1973+
ExecutionProofId::new(1).expect("Valid proof id"),
1974+
];
1975+
let proofs = proof_ids
1976+
.iter()
1977+
.map(|proof_id| self.create_test_execution_proof_with_id(*proof_id))
1978+
.collect::<Vec<_>>();
1979+
1980+
self.chain
1981+
.store
1982+
.put_execution_proofs(&block_root, &proofs)
1983+
.unwrap();
1984+
1985+
let filter_ids = filter.then(|| vec![proof_ids[1].as_u8()]);
1986+
let result = match self
1987+
.client
1988+
.get_execution_proofs(CoreBlockId::Root(block_root), filter_ids.as_deref())
1989+
.await
1990+
{
1991+
Ok(result) => result.unwrap().into_data(),
1992+
Err(e) => panic!("query failed incorrectly: {e:?}"),
1993+
};
1994+
1995+
if filter {
1996+
assert_eq!(result.len(), 1);
1997+
assert_eq!(result[0].proof_id, proof_ids[1]);
1998+
} else {
1999+
assert_eq!(result.len(), proofs.len());
2000+
}
2001+
2002+
self
2003+
}
2004+
2005+
pub async fn test_get_execution_proofs_zkvm_disabled(self) -> Self {
2006+
let block_id = BlockId(CoreBlockId::Head);
2007+
let (block_root, _, _) = block_id.root(&self.chain).unwrap();
2008+
let result = self
2009+
.client
2010+
.get_execution_proofs(CoreBlockId::Root(block_root), None)
2011+
.await;
2012+
2013+
match result {
2014+
Ok(response) => panic!("query should fail: {response:?}"),
2015+
Err(e) => assert_eq!(e.status().unwrap(), 400),
2016+
}
2017+
2018+
self
2019+
}
2020+
19652021
pub async fn test_get_blobs_post_fulu_full_node(self, versioned_hashes: bool) -> Self {
19662022
let block_id = BlockId(CoreBlockId::Head);
19672023
let (block_root, _, _) = block_id.root(&self.chain).unwrap();
@@ -2752,6 +2808,11 @@ impl ApiTester {
27522808

27532809
/// Helper to create a test execution proof for the head block
27542810
fn create_test_execution_proof(&self) -> ExecutionProof {
2811+
let proof_id = ExecutionProofId::new(0).expect("Valid proof id");
2812+
self.create_test_execution_proof_with_id(proof_id)
2813+
}
2814+
2815+
fn create_test_execution_proof_with_id(&self, proof_id: ExecutionProofId) -> ExecutionProof {
27552816
let head = self.chain.head_snapshot();
27562817
let block_root = head.beacon_block_root;
27572818
let slot = head.beacon_block.slot();
@@ -2763,7 +2824,6 @@ impl ApiTester {
27632824
.map(|p| p.block_hash())
27642825
.unwrap_or_else(|_| ExecutionBlockHash::zero());
27652826

2766-
let proof_id = ExecutionProofId::new(0).expect("Valid proof id");
27672827
let proof_data = vec![0u8; 32]; // Dummy proof data
27682828

27692829
ExecutionProof::new(proof_id, slot, block_hash, block_root, proof_data)
@@ -8168,6 +8228,24 @@ async fn get_blob_sidecars_pre_deneb() {
81688228
.await;
81698229
}
81708230

8231+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
8232+
async fn get_execution_proofs() {
8233+
ApiTester::new_with_zkvm()
8234+
.await
8235+
.test_get_execution_proofs(false)
8236+
.await
8237+
.test_get_execution_proofs(true)
8238+
.await;
8239+
}
8240+
8241+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
8242+
async fn get_execution_proofs_zkvm_disabled() {
8243+
ApiTester::new()
8244+
.await
8245+
.test_get_execution_proofs_zkvm_disabled()
8246+
.await;
8247+
}
8248+
81718249
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
81728250
async fn post_validator_liveness_epoch() {
81738251
ApiTester::new()

common/eth2/src/lib.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,17 @@ impl BeaconNodeHttpClient {
12971297
Ok(path)
12981298
}
12991299

1300+
/// Path for `v1/beacon/execution_proofs/{block_id}`
1301+
pub fn get_execution_proofs_path(&self, block_id: BlockId) -> Result<Url, Error> {
1302+
let mut path = self.eth_path(V1)?;
1303+
path.path_segments_mut()
1304+
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
1305+
.push("beacon")
1306+
.push("execution_proofs")
1307+
.push(&block_id.to_string());
1308+
Ok(path)
1309+
}
1310+
13001311
/// Path for `v1/beacon/blinded_blocks/{block_id}`
13011312
pub fn get_beacon_blinded_blocks_path(&self, block_id: BlockId) -> Result<Url, Error> {
13021313
let mut path = self.eth_path(V1)?;
@@ -1376,6 +1387,30 @@ impl BeaconNodeHttpClient {
13761387
.map(|opt| opt.map(BeaconResponse::Unversioned))
13771388
}
13781389

1390+
/// `GET v1/beacon/execution_proofs/{block_id}`
1391+
///
1392+
/// Returns `Ok(None)` on a 404 error.
1393+
pub async fn get_execution_proofs(
1394+
&self,
1395+
block_id: BlockId,
1396+
proof_ids: Option<&[u8]>,
1397+
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<Vec<ExecutionProof>>>, Error>
1398+
{
1399+
let mut path = self.get_execution_proofs_path(block_id)?;
1400+
if let Some(proof_ids) = proof_ids {
1401+
let ids_string = proof_ids
1402+
.iter()
1403+
.map(|id| id.to_string())
1404+
.collect::<Vec<_>>()
1405+
.join(",");
1406+
path.query_pairs_mut().append_pair("proof_ids", &ids_string);
1407+
}
1408+
1409+
self.get_opt(path)
1410+
.await
1411+
.map(|opt| opt.map(BeaconResponse::Unversioned))
1412+
}
1413+
13791414
/// `GET v1/beacon/blinded_blocks/{block_id}`
13801415
///
13811416
/// Returns `Ok(None)` on a 404 error.

common/eth2/src/types.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,13 @@ pub struct BlobIndicesQuery {
688688
pub indices: Option<Vec<u64>>,
689689
}
690690

691+
#[derive(Clone, Deserialize)]
692+
#[serde(deny_unknown_fields)]
693+
pub struct ExecutionProofIdsQuery {
694+
#[serde(default, deserialize_with = "option_query_vec")]
695+
pub proof_ids: Option<Vec<u8>>,
696+
}
697+
691698
#[derive(Clone, Deserialize)]
692699
#[serde(deny_unknown_fields)]
693700
pub struct BlobsVersionedHashesQuery {

0 commit comments

Comments
 (0)