Skip to content

Commit 7c006d8

Browse files
committed
fix(rpc): engine_rest blobs/bodies containers
1 parent 11e6bd0 commit 7c006d8

6 files changed

Lines changed: 158 additions & 83 deletions

File tree

crates/networking/rpc/benches/fixtures.rs

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -945,9 +945,11 @@ pub fn bodies_range_ssz_paris(
945945
})
946946
})
947947
.collect();
948-
entries_vec
949-
.try_into()
950-
.expect("bodies fit MAX_BODIES_PER_REQUEST")
948+
ethrex_rpc::engine_rest::types::bodies::BodiesResponseParis {
949+
entries: entries_vec
950+
.try_into()
951+
.expect("bodies fit MAX_BODIES_PER_REQUEST"),
952+
}
951953
}
952954

953955
/// SSZ-side bodies response for the shanghai → osaka paths (Shanghai shape).
@@ -968,9 +970,11 @@ pub fn bodies_range_ssz_shanghai(
968970
})
969971
})
970972
.collect();
971-
entries_vec
972-
.try_into()
973-
.expect("bodies fit MAX_BODIES_PER_REQUEST")
973+
ethrex_rpc::engine_rest::types::bodies::BodiesResponseShanghai {
974+
entries: entries_vec
975+
.try_into()
976+
.expect("bodies fit MAX_BODIES_PER_REQUEST"),
977+
}
974978
}
975979

976980
/// JSON-side Amsterdam bodies response (`…BodiesByRangeV2`): adds a per-body
@@ -1020,9 +1024,11 @@ pub fn bodies_range_ssz_amsterdam(
10201024
})
10211025
})
10221026
.collect();
1023-
entries_vec
1024-
.try_into()
1025-
.expect("bodies fit MAX_BODIES_PER_REQUEST")
1027+
ethrex_rpc::engine_rest::types::bodies::BodiesResponseAmsterdam {
1028+
entries: entries_vec
1029+
.try_into()
1030+
.expect("bodies fit MAX_BODIES_PER_REQUEST"),
1031+
}
10261032
}
10271033

10281034
#[allow(dead_code)]
@@ -1116,7 +1122,9 @@ pub fn blobs_v1_response_ssz(
11161122
})
11171123
})
11181124
.collect();
1119-
entries.try_into().expect("n <= MAX_BLOBS_REQUEST")
1125+
ethrex_rpc::engine_rest::types::blobs::BlobsV1Response {
1126+
entries: entries.try_into().expect("n <= MAX_BLOBS_REQUEST"),
1127+
}
11201128
}
11211129

11221130
/// SSZ-side `/blobs/v1` all-miss response: zero-padded full-size entries.
@@ -1127,7 +1135,9 @@ pub fn blobs_v1_response_ssz_allmiss(
11271135
use ethrex_rpc::engine_rest::types::blobs::BlobV1Entry;
11281136

11291137
let entries: Vec<BlobV1Entry> = (0..n).map(|_| BlobV1Entry::unavailable()).collect();
1130-
entries.try_into().expect("n <= MAX_BLOBS_REQUEST")
1138+
ethrex_rpc::engine_rest::types::blobs::BlobsV1Response {
1139+
entries: entries.try_into().expect("n <= MAX_BLOBS_REQUEST"),
1140+
}
11311141
}
11321142

11331143
/// JSON-side v1 all-miss response: `n` nulls.
@@ -1200,7 +1210,9 @@ pub fn blobs_v2_response_ssz(
12001210
})
12011211
})
12021212
.collect();
1203-
entries.try_into().expect("n <= MAX_BLOBS_REQUEST")
1213+
ethrex_rpc::engine_rest::types::blobs::BlobsV2Response {
1214+
entries: entries.try_into().expect("n <= MAX_BLOBS_REQUEST"),
1215+
}
12041216
}
12051217

12061218
/// SSZ-side `/blobs/v3` all-miss response: every entry `available == false`
@@ -1214,7 +1226,9 @@ pub fn blobs_v3_response_ssz_allmiss(
12141226
use ethrex_rpc::engine_rest::types::blobs::BlobV2Entry;
12151227

12161228
let entries: Vec<BlobV2Entry> = (0..n).map(|_| BlobV2Entry::unavailable()).collect();
1217-
entries.try_into().expect("n <= MAX_BLOBS_REQUEST")
1229+
ethrex_rpc::engine_rest::types::blobs::BlobsV3Response {
1230+
entries: entries.try_into().expect("n <= MAX_BLOBS_REQUEST"),
1231+
}
12181232
}
12191233

12201234
/// JSON-side v3 all-miss response: `n` nulls.
@@ -1267,5 +1281,7 @@ pub fn blobs_v4_response_ssz(
12671281
}
12681282
})
12691283
.collect();
1270-
entries.try_into().expect("n <= MAX_BLOBS_REQUEST")
1284+
ethrex_rpc::engine_rest::types::blobs::BlobsV4Response {
1285+
entries: entries.try_into().expect("n <= MAX_BLOBS_REQUEST"),
1286+
}
12711287
}

crates/networking/rpc/engine_rest/handlers/blobs.rs

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
//! /blobs/v{1..4} — blob retrieval from mempool, per execution-apis #793.
22
//!
3-
//! Requests are a bare `List[VersionedHash]` (v1/v2/v3) or a `BlobsRequestV4`
4-
//! container (v4). Responses are a bare `List[BlobV*Entry]`. `/blobs/v2` is
3+
//! Requests are single-field SSZ containers wrapping a `List[VersionedHash]`
4+
//! (`BlobsV1Request`/`BlobsV2Request`) or the `BlobsRequestV4` container (v4) —
5+
//! per execution-apis #793, NOT bare top-level lists. Responses are likewise
6+
//! single-field containers (`BlobsV*Response { entries: List[BlobV*Entry] }`).
7+
//! `/blobs/v2` is
58
//! all-or-nothing: if any requested blob is missing the handler returns
69
//! `204 No Content` instead of emitting unavailable entries. `/blobs/v3`
710
//! surfaces missing blobs per entry. `/blobs/v4` requires per-cell data that
@@ -17,8 +20,8 @@ use crate::engine_rest::extractors::Ssz;
1720
use crate::engine_rest::handlers::capabilities::BLOBS_MAX_COUNT;
1821
use crate::engine_rest::responses::SszBody;
1922
use crate::engine_rest::types::blobs::{
20-
BlobAndProofV1, BlobAndProofV2, BlobV1Entry, BlobV2Entry, BlobsRequestV4, BlobsV1Response,
21-
BlobsV2Response, BlobsV3Response, VersionedHashList,
23+
BlobAndProofV1, BlobAndProofV2, BlobV1Entry, BlobV2Entry, BlobsRequestV4, BlobsV1Request,
24+
BlobsV1Response, BlobsV2Request, BlobsV2Response, BlobsV3Response,
2225
};
2326
use crate::rpc::RpcApiContext;
2427

@@ -32,10 +35,7 @@ fn request_hashes(versioned_hashes: &[[u8; 32]]) -> Result<Vec<H256>, ProblemJso
3235
Ok(versioned_hashes.iter().map(|h| H256::from(*h)).collect())
3336
}
3437

35-
pub async fn blobs_v1(
36-
State(ctx): State<RpcApiContext>,
37-
Ssz(req): Ssz<VersionedHashList>,
38-
) -> Response {
38+
pub async fn blobs_v1(State(ctx): State<RpcApiContext>, Ssz(req): Ssz<BlobsV1Request>) -> Response {
3939
// Osaka gate (mirror JSON-RPC getBlobsV1, engine/blobs.rs): /blobs/v1 serves
4040
// whole-blob proofs and is only valid pre-Osaka. After Osaka a blob carries
4141
// cell proofs, so the `proofs[0]` below would be a cell proof rather than a
@@ -60,7 +60,7 @@ pub async fn blobs_v1(
6060
Err(e) => return ProblemJson::internal(&format!("storage: {e}")).into_response(),
6161
}
6262

63-
let hashes = match request_hashes(&req) {
63+
let hashes = match request_hashes(&req.versioned_hashes) {
6464
Ok(h) => h,
6565
Err(p) => return p.into_response(),
6666
};
@@ -92,19 +92,16 @@ pub async fn blobs_v1(
9292
entries.push(entry);
9393
}
9494

95-
match TryInto::<BlobsV1Response>::try_into(entries) {
96-
Ok(resp) => SszBody(resp).into_response(),
95+
match entries.try_into() {
96+
Ok(entries) => SszBody(BlobsV1Response { entries }).into_response(),
9797
Err(_) => ProblemJson::internal("blobs response exceeds MAX_BLOBS_REQUEST").into_response(),
9898
}
9999
}
100100

101101
/// `/blobs/v2` — all-or-nothing (Osaka). If any requested blob is missing the
102102
/// EL MUST return `204 No Content`, mirroring `engine_getBlobsV2`'s `null`.
103-
pub async fn blobs_v2(
104-
State(ctx): State<RpcApiContext>,
105-
Ssz(req): Ssz<VersionedHashList>,
106-
) -> Response {
107-
let hashes = match request_hashes(&req) {
103+
pub async fn blobs_v2(State(ctx): State<RpcApiContext>, Ssz(req): Ssz<BlobsV2Request>) -> Response {
104+
let hashes = match request_hashes(&req.versioned_hashes) {
108105
Ok(h) => h,
109106
Err(p) => return p.into_response(),
110107
};
@@ -141,20 +138,17 @@ pub async fn blobs_v2(
141138
}
142139
}
143140

144-
match TryInto::<BlobsV2Response>::try_into(entries) {
145-
Ok(resp) => SszBody(resp).into_response(),
141+
match entries.try_into() {
142+
Ok(entries) => SszBody(BlobsV2Response { entries }).into_response(),
146143
Err(_) => ProblemJson::internal("blobs response exceeds MAX_BLOBS_REQUEST").into_response(),
147144
}
148145
}
149146

150147
/// `/blobs/v3` — partial responses (Osaka). Missing blobs surface as
151148
/// `available == false` entries; `204 No Content` is reserved for "EL cannot
152149
/// serve the request at all".
153-
pub async fn blobs_v3(
154-
State(ctx): State<RpcApiContext>,
155-
Ssz(req): Ssz<VersionedHashList>,
156-
) -> Response {
157-
let hashes = match request_hashes(&req) {
150+
pub async fn blobs_v3(State(ctx): State<RpcApiContext>, Ssz(req): Ssz<BlobsV2Request>) -> Response {
151+
let hashes = match request_hashes(&req.versioned_hashes) {
158152
Ok(h) => h,
159153
Err(p) => return p.into_response(),
160154
};
@@ -186,8 +180,8 @@ pub async fn blobs_v3(
186180
entries.push(entry);
187181
}
188182

189-
match TryInto::<BlobsV3Response>::try_into(entries) {
190-
Ok(resp) => SszBody(resp).into_response(),
183+
match entries.try_into() {
184+
Ok(entries) => SszBody(BlobsV3Response { entries }).into_response(),
191185
Err(_) => ProblemJson::internal("blobs response exceeds MAX_BLOBS_REQUEST").into_response(),
192186
}
193187
}

crates/networking/rpc/engine_rest/handlers/bodies.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::engine_rest::fork_path::ForkPath;
1616
use crate::engine_rest::handlers::capabilities::BODIES_MAX_COUNT;
1717
use crate::engine_rest::responses::SszBody;
1818
use crate::engine_rest::types::bodies::{
19-
BlockHashList, BodiesResponseAmsterdam, BodiesResponseParis, BodiesResponseShanghai,
19+
BodiesByHashRequest, BodiesResponseAmsterdam, BodiesResponseParis, BodiesResponseShanghai,
2020
BodyAmsterdam, BodyEntryAmsterdam, BodyEntryParis, BodyEntryShanghai, BodyParis, BodyShanghai,
2121
};
2222
use crate::engine_rest::types::common::Bytes20;
@@ -28,8 +28,9 @@ use crate::rpc::RpcApiContext;
2828
pub async fn bodies_by_hash(
2929
ForkPath(fork): ForkPath,
3030
State(ctx): State<RpcApiContext>,
31-
Ssz(hashes): Ssz<BlockHashList>,
31+
Ssz(req): Ssz<BodiesByHashRequest>,
3232
) -> Response {
33+
let hashes = &req.block_hashes;
3334
if hashes.len() > BODIES_MAX_COUNT as usize {
3435
return ProblemJson::payload_too_large(&format!(
3536
"request exceeds BODIES_MAX_COUNT ({BODIES_MAX_COUNT})"
@@ -146,8 +147,8 @@ async fn build_bodies_response(
146147
};
147148
entries.push(entry);
148149
}
149-
match TryInto::<BodiesResponseParis>::try_into(entries) {
150-
Ok(resp) => SszBody(resp).into_response(),
150+
match entries.try_into() {
151+
Ok(entries) => SszBody(BodiesResponseParis { entries }).into_response(),
151152
Err(_) => bodies_overflow().into_response(),
152153
}
153154
}
@@ -165,8 +166,8 @@ async fn build_bodies_response(
165166
};
166167
entries.push(entry);
167168
}
168-
match TryInto::<BodiesResponseShanghai>::try_into(entries) {
169-
Ok(resp) => SszBody(resp).into_response(),
169+
match entries.try_into() {
170+
Ok(entries) => SszBody(BodiesResponseShanghai { entries }).into_response(),
170171
Err(_) => bodies_overflow().into_response(),
171172
}
172173
}
@@ -204,8 +205,8 @@ async fn build_bodies_response(
204205
};
205206
entries.push(entry);
206207
}
207-
match TryInto::<BodiesResponseAmsterdam>::try_into(entries) {
208-
Ok(resp) => SszBody(resp).into_response(),
208+
match entries.try_into() {
209+
Ok(entries) => SszBody(BodiesResponseAmsterdam { entries }).into_response(),
209210
Err(_) => bodies_overflow().into_response(),
210211
}
211212
}

crates/networking/rpc/engine_rest/types/blobs.rs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,23 @@ pub const MAX_BLOBS_REQUEST: usize = 128;
2929

3030
// ── Requests ──────────────────────────────────────────────────────────────────
3131

32-
/// `/blobs/v1`, `/blobs/v2`, `/blobs/v3` request: a bare `List[VersionedHash, N]`.
32+
/// Inner versioned-hash list wrapped by the v1/v2/v3 request containers.
3333
pub type VersionedHashList = SszList<[u8; 32], MAX_BLOBS_REQUEST>;
3434

35+
/// `/blobs/v1` request. Per execution-apis #793 the request is a single-field
36+
/// SSZ **container** wrapping the list (a 4-byte offset precedes the hashes on
37+
/// the wire), NOT a bare top-level list.
38+
#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
39+
pub struct BlobsV1Request {
40+
pub versioned_hashes: VersionedHashList,
41+
}
42+
43+
/// `/blobs/v2` and `/blobs/v3` request — same single-field container as v1.
44+
#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
45+
pub struct BlobsV2Request {
46+
pub versioned_hashes: VersionedHashList,
47+
}
48+
3549
/// `/blobs/v4` request: versioned hashes + a `CELLS_PER_EXT_BLOB`-bit bitarray
3650
/// selecting which cell indices to return.
3751
#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
@@ -127,14 +141,23 @@ impl BlobV2Entry {
127141
}
128142
}
129143

130-
// ── Response aliases (bare top-level lists, per the CL) ────────────────────────
144+
// ── Response containers (single-field, per execution-apis #793) ───────────────
131145

132-
/// `/blobs/v1` response.
133-
pub type BlobsV1Response = SszList<BlobV1Entry, MAX_BLOBS_REQUEST>;
146+
/// `/blobs/v1` response: `{ entries: List[BlobV1Entry, N] }`.
147+
#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
148+
pub struct BlobsV1Response {
149+
pub entries: SszList<BlobV1Entry, MAX_BLOBS_REQUEST>,
150+
}
134151
/// `/blobs/v2` response (all-or-nothing; every entry is `available`).
135-
pub type BlobsV2Response = SszList<BlobV2Entry, MAX_BLOBS_REQUEST>;
152+
#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
153+
pub struct BlobsV2Response {
154+
pub entries: SszList<BlobV2Entry, MAX_BLOBS_REQUEST>,
155+
}
136156
/// `/blobs/v3` response (partial; missing blobs surface as `available == false`).
137-
pub type BlobsV3Response = SszList<BlobV2Entry, MAX_BLOBS_REQUEST>;
157+
#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
158+
pub struct BlobsV3Response {
159+
pub entries: SszList<BlobV2Entry, MAX_BLOBS_REQUEST>,
160+
}
138161

139162
// ── `/blobs/v4` types ─────────────────────────────────────────────────────────
140163
//
@@ -163,5 +186,8 @@ pub struct BlobV4Entry {
163186
pub contents: BlobCellsAndProofs,
164187
}
165188

166-
/// `/blobs/v4` response.
167-
pub type BlobsV4Response = SszList<BlobV4Entry, MAX_BLOBS_REQUEST>;
189+
/// `/blobs/v4` response: `{ entries: List[BlobV4Entry, N] }`.
190+
#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode)]
191+
pub struct BlobsV4Response {
192+
pub entries: SszList<BlobV4Entry, MAX_BLOBS_REQUEST>,
193+
}

crates/networking/rpc/engine_rest/types/bodies.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,16 @@ use super::shanghai::Withdrawal;
1919
/// bodies response (`MAX_BODIES_REQUEST = 2**5`); matches the consensoor CL.
2020
pub const MAX_BODIES_PER_REQUEST: usize = 32;
2121

22-
/// `POST /{fork}/bodies/hash` request body: a bare `List[Hash32, MAX_BODIES_REQUEST]`.
22+
/// Inner block-hash list wrapped by `BodiesByHashRequest`.
2323
pub type BlockHashList = SszList<[u8; 32], MAX_BODIES_PER_REQUEST>;
2424

25+
/// `POST /{fork}/bodies/hash` request. Per execution-apis #793 the request is a
26+
/// single-field SSZ **container** wrapping the list, NOT a bare top-level list.
27+
#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
28+
pub struct BodiesByHashRequest {
29+
pub block_hashes: BlockHashList,
30+
}
31+
2532
// ── Per-fork ExecutionPayloadBody ─────────────────────────────────────────────
2633

2734
/// Paris body: transactions only.
@@ -143,13 +150,22 @@ impl BodyEntryAmsterdam {
143150
}
144151
}
145152

146-
// ── Response aliases (bare top-level lists, per the CL) ───────────────────────
153+
// ── Response containers (single-field, per execution-apis #793) ───────────────
147154
//
148155
// Shared by both `POST /{fork}/bodies/hash` and `GET /{fork}/bodies` (range).
149156

150-
/// Paris bodies response.
151-
pub type BodiesResponseParis = SszList<BodyEntryParis, MAX_BODIES_PER_REQUEST>;
157+
/// Paris bodies response: `{ entries: List[BodyEntryParis, N] }`.
158+
#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
159+
pub struct BodiesResponseParis {
160+
pub entries: SszList<BodyEntryParis, MAX_BODIES_PER_REQUEST>,
161+
}
152162
/// Shanghai/Cancun/Prague/Osaka bodies response.
153-
pub type BodiesResponseShanghai = SszList<BodyEntryShanghai, MAX_BODIES_PER_REQUEST>;
163+
#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
164+
pub struct BodiesResponseShanghai {
165+
pub entries: SszList<BodyEntryShanghai, MAX_BODIES_PER_REQUEST>,
166+
}
154167
/// Amsterdam bodies response.
155-
pub type BodiesResponseAmsterdam = SszList<BodyEntryAmsterdam, MAX_BODIES_PER_REQUEST>;
168+
#[derive(Debug, Clone, PartialEq, Eq, SszEncode, SszDecode, HashTreeRoot)]
169+
pub struct BodiesResponseAmsterdam {
170+
pub entries: SszList<BodyEntryAmsterdam, MAX_BODIES_PER_REQUEST>,
171+
}

0 commit comments

Comments
 (0)