Skip to content

Commit 7e6df3a

Browse files
committed
Exclude empty requests and add back prefix
1 parent 23d331b commit 7e6df3a

File tree

3 files changed

+91
-26
lines changed

3 files changed

+91
-26
lines changed

beacon_node/execution_layer/src/engine_api/json_structures.rs

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use superstruct::superstruct;
77
use types::beacon_block_body::KzgCommitments;
88
use types::blob_sidecar::BlobsList;
99
use types::execution_requests::{
10-
ConsolidationRequests, DepositRequests, RequestPrefix, WithdrawalRequests,
10+
ConsolidationRequests, DepositRequests, RequestType, WithdrawalRequests,
1111
};
1212
use types::{Blob, FixedVector, KzgProof, Unsigned};
1313

@@ -341,6 +341,15 @@ impl<E: EthSpec> From<JsonExecutionPayload<E>> for ExecutionPayload<E> {
341341
}
342342
}
343343

344+
#[derive(Debug, Clone)]
345+
pub enum RequestsError {
346+
InvalidHex(hex::FromHexError),
347+
EmptyRequest(usize),
348+
InvalidOrdering,
349+
InvalidPrefix(u8),
350+
DecodeError(String),
351+
}
352+
344353
/// Format of `ExecutionRequests` received over the engine api.
345354
///
346355
/// Array of ssz-encoded requests list encoded as hex bytes.
@@ -355,33 +364,62 @@ impl<E: EthSpec> From<JsonExecutionPayload<E>> for ExecutionPayload<E> {
355364
pub struct JsonExecutionRequests(pub Vec<String>);
356365

357366
impl<E: EthSpec> TryFrom<JsonExecutionRequests> for ExecutionRequests<E> {
358-
type Error = String;
367+
type Error = RequestsError;
359368

360369
fn try_from(value: JsonExecutionRequests) -> Result<Self, Self::Error> {
361370
let mut requests = ExecutionRequests::default();
362-
371+
let mut prev_prefix: Option<RequestType> = None;
363372
for (i, request) in value.0.into_iter().enumerate() {
364373
// hex string
365374
let decoded_bytes = hex::decode(request.strip_prefix("0x").unwrap_or(&request))
366-
.map_err(|e| format!("Invalid hex {:?}", e))?;
367-
match RequestPrefix::from_prefix(i as u8) {
368-
Some(RequestPrefix::Deposit) => {
369-
requests.deposits = DepositRequests::<E>::from_ssz_bytes(&decoded_bytes)
370-
.map_err(|e| format!("Failed to decode DepositRequest from EL: {:?}", e))?;
375+
.map_err(RequestsError::InvalidHex)?;
376+
377+
// The first byte of each element is the `request_type` and the remaining bytes are the `request_data`.
378+
// Elements with empty `request_data` **MUST** be excluded from the list.
379+
let Some((prefix_byte, request_bytes)) = decoded_bytes.split_first() else {
380+
return Err(RequestsError::EmptyRequest(i));
381+
};
382+
if request_bytes.is_empty() {
383+
return Err(RequestsError::EmptyRequest(i));
384+
}
385+
// Elements of the list **MUST** be ordered by `request_type` in ascending order
386+
let current_prefix = RequestType::from_prefix(*prefix_byte)
387+
.ok_or(RequestsError::InvalidPrefix(*prefix_byte))?;
388+
if let Some(prev) = prev_prefix {
389+
if prev.to_prefix() >= current_prefix.to_prefix() {
390+
return Err(RequestsError::InvalidOrdering);
371391
}
372-
Some(RequestPrefix::Withdrawal) => {
373-
requests.withdrawals = WithdrawalRequests::<E>::from_ssz_bytes(&decoded_bytes)
392+
}
393+
prev_prefix = Some(current_prefix);
394+
395+
match current_prefix {
396+
RequestType::Deposit => {
397+
requests.deposits = DepositRequests::<E>::from_ssz_bytes(request_bytes)
374398
.map_err(|e| {
375-
format!("Failed to decode WithdrawalRequest from EL: {:?}", e)
399+
RequestsError::DecodeError(format!(
400+
"Failed to decode DepositRequest from EL: {:?}",
401+
e
402+
))
376403
})?;
377404
}
378-
Some(RequestPrefix::Consolidation) => {
405+
RequestType::Withdrawal => {
406+
requests.withdrawals = WithdrawalRequests::<E>::from_ssz_bytes(request_bytes)
407+
.map_err(|e| {
408+
RequestsError::DecodeError(format!(
409+
"Failed to decode WithdrawalRequest from EL: {:?}",
410+
e
411+
))
412+
})?;
413+
}
414+
RequestType::Consolidation => {
379415
requests.consolidations =
380-
ConsolidationRequests::<E>::from_ssz_bytes(&decoded_bytes).map_err(
381-
|e| format!("Failed to decode ConsolidationRequest from EL: {:?}", e),
382-
)?;
416+
ConsolidationRequests::<E>::from_ssz_bytes(request_bytes).map_err(|e| {
417+
RequestsError::DecodeError(format!(
418+
"Failed to decode ConsolidationRequest from EL: {:?}",
419+
e
420+
))
421+
})?;
383422
}
384-
None => return Err("Empty requests string".to_string()),
385423
}
386424
}
387425
Ok(requests)
@@ -448,7 +486,9 @@ impl<E: EthSpec> TryFrom<JsonGetPayloadResponse<E>> for GetPayloadResponse<E> {
448486
block_value: response.block_value,
449487
blobs_bundle: response.blobs_bundle.into(),
450488
should_override_builder: response.should_override_builder,
451-
requests: response.execution_requests.try_into()?,
489+
requests: response.execution_requests.try_into().map_err(|e| {
490+
format!("Failed to convert json to execution requests : {:?}", e)
491+
})?,
452492
}))
453493
}
454494
}

consensus/types/src/execution_requests.rs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,29 @@ impl<E: EthSpec> ExecutionRequests<E> {
4343
/// Returns the encoding according to EIP-7685 to send
4444
/// to the execution layer over the engine api.
4545
pub fn get_execution_requests_list(&self) -> Vec<Bytes> {
46-
let deposit_bytes = Bytes::from(self.deposits.as_ssz_bytes());
47-
let withdrawal_bytes = Bytes::from(self.withdrawals.as_ssz_bytes());
48-
let consolidation_bytes = Bytes::from(self.consolidations.as_ssz_bytes());
49-
vec![deposit_bytes, withdrawal_bytes, consolidation_bytes]
46+
let mut requests_list = Vec::new();
47+
if !self.deposits.is_empty() {
48+
requests_list.push(Bytes::from_iter(
49+
[RequestType::Deposit.to_prefix()]
50+
.into_iter()
51+
.chain(self.deposits.as_ssz_bytes()),
52+
));
53+
}
54+
if !self.withdrawals.is_empty() {
55+
requests_list.push(Bytes::from_iter(
56+
[RequestType::Withdrawal.to_prefix()]
57+
.into_iter()
58+
.chain(self.withdrawals.as_ssz_bytes()),
59+
));
60+
}
61+
if !self.consolidations.is_empty() {
62+
requests_list.push(Bytes::from_iter(
63+
[RequestType::Consolidation.to_prefix()]
64+
.into_iter()
65+
.chain(self.consolidations.as_ssz_bytes()),
66+
));
67+
}
68+
requests_list
5069
}
5170

5271
/// Generate the execution layer `requests_hash` based on EIP-7685.
@@ -55,9 +74,8 @@ impl<E: EthSpec> ExecutionRequests<E> {
5574
pub fn requests_hash(&self) -> Hash256 {
5675
let mut hasher = DynamicContext::new();
5776

58-
for (i, request) in self.get_execution_requests_list().iter().enumerate() {
77+
for request in self.get_execution_requests_list().iter() {
5978
let mut request_hasher = DynamicContext::new();
60-
request_hasher.update(&[i as u8]);
6179
request_hasher.update(request);
6280
let request_hash = request_hasher.finalize();
6381

@@ -70,13 +88,13 @@ impl<E: EthSpec> ExecutionRequests<E> {
7088

7189
/// This is used to index into the `execution_requests` array.
7290
#[derive(Debug, Copy, Clone)]
73-
pub enum RequestPrefix {
91+
pub enum RequestType {
7492
Deposit,
7593
Withdrawal,
7694
Consolidation,
7795
}
7896

79-
impl RequestPrefix {
97+
impl RequestType {
8098
pub fn from_prefix(prefix: u8) -> Option<Self> {
8199
match prefix {
82100
0 => Some(Self::Deposit),
@@ -85,6 +103,13 @@ impl RequestPrefix {
85103
_ => None,
86104
}
87105
}
106+
pub fn to_prefix(&self) -> u8 {
107+
match self {
108+
Self::Deposit => 0,
109+
Self::Withdrawal => 1,
110+
Self::Consolidation => 2,
111+
}
112+
}
88113
}
89114

90115
#[cfg(test)]

consensus/types/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ pub use crate::execution_payload_header::{
170170
ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderRef,
171171
ExecutionPayloadHeaderRefMut,
172172
};
173-
pub use crate::execution_requests::{ExecutionRequests, RequestPrefix};
173+
pub use crate::execution_requests::{ExecutionRequests, RequestType};
174174
pub use crate::fork::Fork;
175175
pub use crate::fork_context::ForkContext;
176176
pub use crate::fork_data::ForkData;

0 commit comments

Comments
 (0)