Skip to content

Commit 93f9c2c

Browse files
authored
Execution requests with prefix (#6801)
* Exclude empty requests and add back prefix * cleanup * fix after rebase
1 parent 06e4d22 commit 93f9c2c

File tree

3 files changed

+98
-36
lines changed

3 files changed

+98
-36
lines changed

beacon_node/execution_layer/src/engine_api/json_structures.rs

Lines changed: 62 additions & 25 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

@@ -401,47 +401,80 @@ impl<E: EthSpec> From<JsonExecutionPayload<E>> for ExecutionPayload<E> {
401401
}
402402
}
403403

404+
#[derive(Debug, Clone)]
405+
pub enum RequestsError {
406+
InvalidHex(hex::FromHexError),
407+
EmptyRequest(usize),
408+
InvalidOrdering,
409+
InvalidPrefix(u8),
410+
DecodeError(String),
411+
}
412+
404413
/// Format of `ExecutionRequests` received over the engine api.
405414
///
406-
/// Array of ssz-encoded requests list encoded as hex bytes.
407-
/// The prefix of the request type is used to index into the array.
408-
///
409-
/// For e.g. [0xab, 0xcd, 0xef]
410-
/// Here, 0xab are the deposits bytes (prefix and index == 0)
411-
/// 0xcd are the withdrawals bytes (prefix and index == 1)
412-
/// 0xef are the consolidations bytes (prefix and index == 2)
415+
/// Array of ssz-encoded requests list encoded as hex bytes prefixed
416+
/// with a `RequestType`
413417
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
414418
#[serde(transparent)]
415419
pub struct JsonExecutionRequests(pub Vec<String>);
416420

417421
impl<E: EthSpec> TryFrom<JsonExecutionRequests> for ExecutionRequests<E> {
418-
type Error = String;
422+
type Error = RequestsError;
419423

420424
fn try_from(value: JsonExecutionRequests) -> Result<Self, Self::Error> {
421425
let mut requests = ExecutionRequests::default();
422-
426+
let mut prev_prefix: Option<RequestType> = None;
423427
for (i, request) in value.0.into_iter().enumerate() {
424428
// hex string
425429
let decoded_bytes = hex::decode(request.strip_prefix("0x").unwrap_or(&request))
426-
.map_err(|e| format!("Invalid hex {:?}", e))?;
427-
match RequestPrefix::from_prefix(i as u8) {
428-
Some(RequestPrefix::Deposit) => {
429-
requests.deposits = DepositRequests::<E>::from_ssz_bytes(&decoded_bytes)
430-
.map_err(|e| format!("Failed to decode DepositRequest from EL: {:?}", e))?;
430+
.map_err(RequestsError::InvalidHex)?;
431+
432+
// The first byte of each element is the `request_type` and the remaining bytes are the `request_data`.
433+
// Elements with empty `request_data` **MUST** be excluded from the list.
434+
let Some((prefix_byte, request_bytes)) = decoded_bytes.split_first() else {
435+
return Err(RequestsError::EmptyRequest(i));
436+
};
437+
if request_bytes.is_empty() {
438+
return Err(RequestsError::EmptyRequest(i));
439+
}
440+
// Elements of the list **MUST** be ordered by `request_type` in ascending order
441+
let current_prefix = RequestType::from_u8(*prefix_byte)
442+
.ok_or(RequestsError::InvalidPrefix(*prefix_byte))?;
443+
if let Some(prev) = prev_prefix {
444+
if prev.to_u8() >= current_prefix.to_u8() {
445+
return Err(RequestsError::InvalidOrdering);
431446
}
432-
Some(RequestPrefix::Withdrawal) => {
433-
requests.withdrawals = WithdrawalRequests::<E>::from_ssz_bytes(&decoded_bytes)
447+
}
448+
prev_prefix = Some(current_prefix);
449+
450+
match current_prefix {
451+
RequestType::Deposit => {
452+
requests.deposits = DepositRequests::<E>::from_ssz_bytes(request_bytes)
434453
.map_err(|e| {
435-
format!("Failed to decode WithdrawalRequest from EL: {:?}", e)
454+
RequestsError::DecodeError(format!(
455+
"Failed to decode DepositRequest from EL: {:?}",
456+
e
457+
))
436458
})?;
437459
}
438-
Some(RequestPrefix::Consolidation) => {
460+
RequestType::Withdrawal => {
461+
requests.withdrawals = WithdrawalRequests::<E>::from_ssz_bytes(request_bytes)
462+
.map_err(|e| {
463+
RequestsError::DecodeError(format!(
464+
"Failed to decode WithdrawalRequest from EL: {:?}",
465+
e
466+
))
467+
})?;
468+
}
469+
RequestType::Consolidation => {
439470
requests.consolidations =
440-
ConsolidationRequests::<E>::from_ssz_bytes(&decoded_bytes).map_err(
441-
|e| format!("Failed to decode ConsolidationRequest from EL: {:?}", e),
442-
)?;
471+
ConsolidationRequests::<E>::from_ssz_bytes(request_bytes).map_err(|e| {
472+
RequestsError::DecodeError(format!(
473+
"Failed to decode ConsolidationRequest from EL: {:?}",
474+
e
475+
))
476+
})?;
443477
}
444-
None => return Err("Empty requests string".to_string()),
445478
}
446479
}
447480
Ok(requests)
@@ -510,7 +543,9 @@ impl<E: EthSpec> TryFrom<JsonGetPayloadResponse<E>> for GetPayloadResponse<E> {
510543
block_value: response.block_value,
511544
blobs_bundle: response.blobs_bundle.into(),
512545
should_override_builder: response.should_override_builder,
513-
requests: response.execution_requests.try_into()?,
546+
requests: response.execution_requests.try_into().map_err(|e| {
547+
format!("Failed to convert json to execution requests : {:?}", e)
548+
})?,
514549
}))
515550
}
516551
JsonGetPayloadResponse::V5(response) => {
@@ -519,7 +554,9 @@ impl<E: EthSpec> TryFrom<JsonGetPayloadResponse<E>> for GetPayloadResponse<E> {
519554
block_value: response.block_value,
520555
blobs_bundle: response.blobs_bundle.into(),
521556
should_override_builder: response.should_override_builder,
522-
requests: response.execution_requests.try_into()?,
557+
requests: response.execution_requests.try_into().map_err(|e| {
558+
format!("Failed to convert json to execution requests {:?}", e)
559+
})?,
523560
}))
524561
}
525562
}

consensus/types/src/execution_requests.rs

Lines changed: 35 additions & 10 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_u8()]
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_u8()]
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_u8()]
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

@@ -68,23 +86,30 @@ impl<E: EthSpec> ExecutionRequests<E> {
6886
}
6987
}
7088

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

79-
impl RequestPrefix {
80-
pub fn from_prefix(prefix: u8) -> Option<Self> {
97+
impl RequestType {
98+
pub fn from_u8(prefix: u8) -> Option<Self> {
8199
match prefix {
82100
0 => Some(Self::Deposit),
83101
1 => Some(Self::Withdrawal),
84102
2 => Some(Self::Consolidation),
85103
_ => None,
86104
}
87105
}
106+
pub fn to_u8(&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
@@ -172,7 +172,7 @@ pub use crate::execution_payload_header::{
172172
ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu,
173173
ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut,
174174
};
175-
pub use crate::execution_requests::{ExecutionRequests, RequestPrefix};
175+
pub use crate::execution_requests::{ExecutionRequests, RequestType};
176176
pub use crate::fork::Fork;
177177
pub use crate::fork_context::ForkContext;
178178
pub use crate::fork_data::ForkData;

0 commit comments

Comments
 (0)