Skip to content

Commit a2e7fcf

Browse files
gakonstampcode-com
andauthored
feat(rpc-types-engine): add ExecutionPayloadBodyV2 for EIP-7928 (#3636)
Adds ExecutionPayloadBodyV2 type which extends V1 with a blockAccessList field for the Block Access List feature introduced in EIP-7928. - Add ExecutionPayloadBodyV2 struct with transactions, withdrawals, and block_access_list fields - Add ExecutionPayloadBodiesV2 type alias - Add from_block and new constructors - Add bidirectional conversion between V1 and V2 - Add V2 endpoints to CAPABILITIES list - Add serde and conversion tests Amp-Thread-ID: https://ampcode.com/threads/T-019c2103-9dd3-737f-a8e4-86e0842c5036 Co-authored-by: Amp <amp@ampcode.com>
1 parent 9c4277a commit a2e7fcf

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed

crates/rpc-types-engine/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,6 @@ pub const CAPABILITIES: &[&str] = &[
6666
"engine_newPayloadV4",
6767
"engine_getPayloadBodiesByHashV1",
6868
"engine_getPayloadBodiesByRangeV1",
69+
"engine_getPayloadBodiesByHashV2",
70+
"engine_getPayloadBodiesByRangeV2",
6971
];

crates/rpc-types-engine/src/payload.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ use core::iter::{FromIterator, IntoIterator};
2626
/// The execution payload body response that allows for `null` values.
2727
pub type ExecutionPayloadBodiesV1 = Vec<Option<ExecutionPayloadBodyV1>>;
2828

29+
/// The execution payload body V2 response that allows for `null` values.
30+
///
31+
/// See also: <https://eips.ethereum.org/EIPS/eip-7928>
32+
pub type ExecutionPayloadBodiesV2 = Vec<Option<ExecutionPayloadBodyV2>>;
33+
2934
/// And 8-byte identifier for an execution payload.
3035
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
3136
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -2292,6 +2297,69 @@ impl<T: Encodable2718, H> From<Block<T, H>> for ExecutionPayloadBodyV1 {
22922297
}
22932298
}
22942299

2300+
/// This structure contains a body of an execution payload (V2).
2301+
///
2302+
/// V2 extends V1 with the `blockAccessList` field introduced in EIP-7928.
2303+
///
2304+
/// See also: <https://eips.ethereum.org/EIPS/eip-7928>
2305+
#[derive(Clone, Debug, PartialEq, Eq)]
2306+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2307+
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2308+
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
2309+
pub struct ExecutionPayloadBodyV2 {
2310+
/// Enveloped encoded transactions.
2311+
pub transactions: Vec<Bytes>,
2312+
/// All withdrawals in the block.
2313+
///
2314+
/// Will always be `None` if pre shanghai.
2315+
pub withdrawals: Option<Vec<Withdrawal>>,
2316+
/// The RLP-encoded block access list.
2317+
///
2318+
/// Will be `None` for pre-Amsterdam blocks or when data has been pruned.
2319+
pub block_access_list: Option<Bytes>,
2320+
}
2321+
2322+
impl ExecutionPayloadBodyV2 {
2323+
/// Creates an [`ExecutionPayloadBodyV2`] from the given withdrawals, transactions, and block
2324+
/// access list.
2325+
pub fn new<'a, T>(
2326+
withdrawals: Option<Withdrawals>,
2327+
transactions: impl IntoIterator<Item = &'a T>,
2328+
block_access_list: Option<Bytes>,
2329+
) -> Self
2330+
where
2331+
T: Encodable2718 + 'a,
2332+
{
2333+
Self {
2334+
transactions: transactions.into_iter().map(|tx| tx.encoded_2718().into()).collect(),
2335+
withdrawals: withdrawals.map(Withdrawals::into_inner),
2336+
block_access_list,
2337+
}
2338+
}
2339+
2340+
/// Converts a [`alloy_consensus::Block`] into an execution payload body, with an optional
2341+
/// block access list.
2342+
pub fn from_block<T: Encodable2718, H>(
2343+
block: Block<T, H>,
2344+
block_access_list: Option<Bytes>,
2345+
) -> Self {
2346+
let BlockBody { withdrawals, transactions, .. } = block.into_body();
2347+
Self::new(withdrawals, transactions.iter(), block_access_list)
2348+
}
2349+
}
2350+
2351+
impl From<ExecutionPayloadBodyV1> for ExecutionPayloadBodyV2 {
2352+
fn from(v1: ExecutionPayloadBodyV1) -> Self {
2353+
Self { transactions: v1.transactions, withdrawals: v1.withdrawals, block_access_list: None }
2354+
}
2355+
}
2356+
2357+
impl From<ExecutionPayloadBodyV2> for ExecutionPayloadBodyV1 {
2358+
fn from(v2: ExecutionPayloadBodyV2) -> Self {
2359+
Self { transactions: v2.transactions, withdrawals: v2.withdrawals }
2360+
}
2361+
}
2362+
22952363
/// This structure contains the attributes required to initiate a payload build process in the
22962364
/// context of an `engine_forkchoiceUpdated` call.
22972365
#[derive(Clone, Debug, Default, PartialEq, Eq)]
@@ -3398,4 +3466,73 @@ mod tests {
33983466
assert_eq!(with_encoded.encoded_bytes(), &transaction);
33993467
}
34003468
}
3469+
3470+
#[test]
3471+
#[cfg(feature = "serde")]
3472+
fn serde_execution_payload_body_v2() {
3473+
let body = ExecutionPayloadBodyV2 {
3474+
transactions: vec![Bytes::from(vec![0x01, 0x02, 0x03])],
3475+
withdrawals: Some(vec![Withdrawal {
3476+
index: 1,
3477+
validator_index: 2,
3478+
address: Address::default(),
3479+
amount: 100,
3480+
}]),
3481+
block_access_list: Some(Bytes::from(vec![0xaa, 0xbb, 0xcc])),
3482+
};
3483+
3484+
let serialized = serde_json::to_string(&body).unwrap();
3485+
let deserialized: ExecutionPayloadBodyV2 = serde_json::from_str(&serialized).unwrap();
3486+
assert_eq!(deserialized, body);
3487+
}
3488+
3489+
#[test]
3490+
#[cfg(feature = "serde")]
3491+
fn serde_execution_payload_body_v2_null_fields() {
3492+
let body = ExecutionPayloadBodyV2 {
3493+
transactions: vec![],
3494+
withdrawals: None,
3495+
block_access_list: None,
3496+
};
3497+
3498+
let serialized = serde_json::to_string(&body).unwrap();
3499+
let deserialized: ExecutionPayloadBodyV2 = serde_json::from_str(&serialized).unwrap();
3500+
assert_eq!(deserialized, body);
3501+
}
3502+
3503+
#[test]
3504+
fn execution_payload_body_v1_to_v2_conversion() {
3505+
let v1 = ExecutionPayloadBodyV1 {
3506+
transactions: vec![Bytes::from(vec![0x01, 0x02])],
3507+
withdrawals: Some(vec![Withdrawal {
3508+
index: 1,
3509+
validator_index: 2,
3510+
address: Address::default(),
3511+
amount: 100,
3512+
}]),
3513+
};
3514+
3515+
let v2: ExecutionPayloadBodyV2 = v1.clone().into();
3516+
assert_eq!(v2.transactions, v1.transactions);
3517+
assert_eq!(v2.withdrawals, v1.withdrawals);
3518+
assert_eq!(v2.block_access_list, None);
3519+
}
3520+
3521+
#[test]
3522+
fn execution_payload_body_v2_to_v1_conversion() {
3523+
let v2 = ExecutionPayloadBodyV2 {
3524+
transactions: vec![Bytes::from(vec![0x01, 0x02])],
3525+
withdrawals: Some(vec![Withdrawal {
3526+
index: 1,
3527+
validator_index: 2,
3528+
address: Address::default(),
3529+
amount: 100,
3530+
}]),
3531+
block_access_list: Some(Bytes::from(vec![0xaa, 0xbb])),
3532+
};
3533+
3534+
let v1: ExecutionPayloadBodyV1 = v2.clone().into();
3535+
assert_eq!(v1.transactions, v2.transactions);
3536+
assert_eq!(v1.withdrawals, v2.withdrawals);
3537+
}
34013538
}

0 commit comments

Comments
 (0)