arb_payload/
lib.rs

1//! Arbitrum payload and engine types.
2//!
3//! Defines the payload attributes, built payload, payload types, and engine
4//! types used by the engine API and block construction pipeline.
5
6use std::{marker::PhantomData, sync::Arc};
7
8use alloy_eips::{eip4895::Withdrawal, eip7685::Requests};
9use alloy_primitives::{B256, Bytes, U256};
10use alloy_rpc_types_engine::{
11    BlobsBundleV1, BlobsBundleV2, ExecutionData, ExecutionPayload as AlloyExecutionPayload,
12    ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4,
13    ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6, ExecutionPayloadFieldV2,
14    ExecutionPayloadV1, ExecutionPayloadV3, PayloadAttributes as AlloyPayloadAttributes, PayloadId,
15};
16use arb_primitives::ArbPrimitives;
17use reth_engine_primitives::EngineTypes;
18use reth_payload_primitives::{
19    BuiltPayload, PayloadAttributes as PayloadAttributesTrait, PayloadTypes,
20};
21use reth_primitives_traits::{NodePrimitives, SealedBlock};
22use serde::{Deserialize, Serialize};
23
24/// Type alias for the Arbitrum block type.
25pub type ArbBlock = <ArbPrimitives as NodePrimitives>::Block;
26
27// ── Payload Attributes ────────────────────────────────────────────────────────
28
29/// Arbitrum-specific payload attributes extending the standard engine API.
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct ArbPayloadAttributes {
33    /// Standard Ethereum payload attributes.
34    #[serde(flatten)]
35    pub inner: AlloyPayloadAttributes,
36    /// Sequencer transactions for this block.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub transactions: Option<Vec<Bytes>>,
39    /// Whether to exclude the transaction pool.
40    #[serde(default)]
41    pub no_tx_pool: bool,
42}
43
44impl PayloadAttributesTrait for ArbPayloadAttributes {
45    fn timestamp(&self) -> u64 {
46        self.inner.timestamp
47    }
48
49    fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
50        self.inner.withdrawals.as_ref()
51    }
52
53    fn parent_beacon_block_root(&self) -> Option<B256> {
54        self.inner.parent_beacon_block_root
55    }
56
57    fn payload_id(&self, parent_hash: &B256) -> PayloadId {
58        arb_payload_id(parent_hash, self)
59    }
60}
61
62// ── Payload ID Computation ────────────────────────────────────────────────────
63
64/// Compute a unique payload ID from the parent hash and attributes.
65pub fn arb_payload_id(parent: &B256, attributes: &ArbPayloadAttributes) -> PayloadId {
66    use alloy_rlp::Encodable;
67    use sha2::Digest;
68
69    let mut hasher = sha2::Sha256::new();
70    hasher.update(parent.as_slice());
71    hasher.update(attributes.inner.timestamp.to_be_bytes());
72    hasher.update(attributes.inner.prev_randao.as_slice());
73    hasher.update(attributes.inner.suggested_fee_recipient.as_slice());
74    if let Some(withdrawals) = &attributes.inner.withdrawals {
75        let mut buf = Vec::new();
76        withdrawals.encode(&mut buf);
77        hasher.update(buf);
78    }
79    if let Some(root) = attributes.inner.parent_beacon_block_root {
80        hasher.update(root);
81    }
82    // Include Arbitrum-specific fields in the payload ID.
83    if attributes.no_tx_pool {
84        hasher.update([1u8]);
85    }
86    if let Some(txs) = &attributes.transactions {
87        for tx in txs {
88            hasher.update(tx.as_ref());
89        }
90    }
91
92    let out = hasher.finalize();
93    PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
94}
95
96// ── Built Payload ─────────────────────────────────────────────────────────────
97
98/// A built Arbitrum payload ready to be sealed.
99#[derive(Debug, Clone)]
100pub struct ArbBuiltPayload {
101    /// Payload identifier.
102    pub id: PayloadId,
103    /// The sealed block.
104    pub block: Arc<SealedBlock<ArbBlock>>,
105    /// Total fees collected.
106    pub fees: U256,
107    /// Execution requests, if any.
108    pub requests: Option<Requests>,
109}
110
111impl ArbBuiltPayload {
112    /// Create a new built payload.
113    pub fn new(id: PayloadId, block: Arc<SealedBlock<ArbBlock>>, fees: U256) -> Self {
114        Self {
115            id,
116            block,
117            fees,
118            requests: None,
119        }
120    }
121
122    /// Set execution requests on this payload.
123    pub fn with_requests(mut self, requests: Option<Requests>) -> Self {
124        self.requests = requests;
125        self
126    }
127}
128
129impl BuiltPayload for ArbBuiltPayload {
130    type Primitives = ArbPrimitives;
131
132    fn block(&self) -> &SealedBlock<ArbBlock> {
133        &self.block
134    }
135
136    fn fees(&self) -> U256 {
137        self.fees
138    }
139
140    fn requests(&self) -> Option<Requests> {
141        self.requests.clone()
142    }
143}
144
145// ── Conversion Error ──────────────────────────────────────────────────────────
146
147/// Error when converting built payloads to envelope types.
148#[derive(Debug, Clone, thiserror::Error)]
149#[error("payload conversion failed")]
150pub struct ArbPayloadConversionError;
151
152// ── From/TryFrom for execution payload envelopes ──────────────────────────────
153
154// V1
155impl From<ArbBuiltPayload> for ExecutionPayloadV1 {
156    fn from(value: ArbBuiltPayload) -> Self {
157        Self::from_block_unchecked(
158            value.block.hash(),
159            &Arc::unwrap_or_clone(value.block).into_block(),
160        )
161    }
162}
163
164// V2
165impl From<ArbBuiltPayload> for ExecutionPayloadEnvelopeV2 {
166    fn from(value: ArbBuiltPayload) -> Self {
167        let ArbBuiltPayload { block, fees, .. } = value;
168        Self {
169            block_value: fees,
170            execution_payload: ExecutionPayloadFieldV2::from_block_unchecked(
171                block.hash(),
172                &Arc::unwrap_or_clone(block).into_block(),
173            ),
174        }
175    }
176}
177
178// V3
179impl TryFrom<ArbBuiltPayload> for ExecutionPayloadEnvelopeV3 {
180    type Error = ArbPayloadConversionError;
181
182    fn try_from(value: ArbBuiltPayload) -> Result<Self, Self::Error> {
183        let ArbBuiltPayload { block, fees, .. } = value;
184        Ok(Self {
185            execution_payload: ExecutionPayloadV3::from_block_unchecked(
186                block.hash(),
187                &Arc::unwrap_or_clone(block).into_block(),
188            ),
189            block_value: fees,
190            should_override_builder: false,
191            blobs_bundle: BlobsBundleV1::empty(),
192        })
193    }
194}
195
196// V4
197impl TryFrom<ArbBuiltPayload> for ExecutionPayloadEnvelopeV4 {
198    type Error = ArbPayloadConversionError;
199
200    fn try_from(value: ArbBuiltPayload) -> Result<Self, Self::Error> {
201        let requests = value.requests.clone().unwrap_or_default();
202        let v3: ExecutionPayloadEnvelopeV3 = value.try_into()?;
203        Ok(Self {
204            execution_requests: requests,
205            envelope_inner: v3,
206        })
207    }
208}
209
210// V5
211impl TryFrom<ArbBuiltPayload> for ExecutionPayloadEnvelopeV5 {
212    type Error = ArbPayloadConversionError;
213
214    fn try_from(value: ArbBuiltPayload) -> Result<Self, Self::Error> {
215        let ArbBuiltPayload {
216            block,
217            fees,
218            requests,
219            ..
220        } = value;
221        Ok(Self {
222            execution_payload: ExecutionPayloadV3::from_block_unchecked(
223                block.hash(),
224                &Arc::unwrap_or_clone(block).into_block(),
225            ),
226            block_value: fees,
227            should_override_builder: false,
228            blobs_bundle: BlobsBundleV2::empty(),
229            execution_requests: requests.unwrap_or_default(),
230        })
231    }
232}
233
234// V6
235impl TryFrom<ArbBuiltPayload> for ExecutionPayloadEnvelopeV6 {
236    type Error = ArbPayloadConversionError;
237
238    fn try_from(_value: ArbBuiltPayload) -> Result<Self, Self::Error> {
239        Err(ArbPayloadConversionError)
240    }
241}
242
243// ── Payload Types ─────────────────────────────────────────────────────────────
244
245/// Payload types for the Arbitrum engine.
246#[derive(Debug, Default, Clone, Serialize, Deserialize)]
247#[non_exhaustive]
248pub struct ArbPayloadTypes;
249
250impl PayloadTypes for ArbPayloadTypes {
251    type ExecutionData = ExecutionData;
252    type BuiltPayload = ArbBuiltPayload;
253    type PayloadAttributes = ArbPayloadAttributes;
254
255    fn block_to_payload(
256        block: SealedBlock<
257            <<Self::BuiltPayload as BuiltPayload>::Primitives as NodePrimitives>::Block,
258        >,
259    ) -> Self::ExecutionData {
260        let (payload, sidecar) =
261            AlloyExecutionPayload::from_block_unchecked(block.hash(), &block.into_block());
262        ExecutionData { payload, sidecar }
263    }
264}
265
266// ── Engine Types ──────────────────────────────────────────────────────────────
267
268/// Engine types for the Arbitrum consensus engine.
269#[derive(Debug, Default, Clone, Serialize, Deserialize)]
270#[non_exhaustive]
271pub struct ArbEngineTypes<T: PayloadTypes = ArbPayloadTypes> {
272    _marker: PhantomData<T>,
273}
274
275impl<T: PayloadTypes<ExecutionData = ExecutionData>> PayloadTypes for ArbEngineTypes<T>
276where
277    T::BuiltPayload: BuiltPayload<Primitives: NodePrimitives<Block = ArbBlock>>,
278{
279    type ExecutionData = T::ExecutionData;
280    type BuiltPayload = T::BuiltPayload;
281    type PayloadAttributes = T::PayloadAttributes;
282
283    fn block_to_payload(
284        block: SealedBlock<
285            <<Self::BuiltPayload as BuiltPayload>::Primitives as NodePrimitives>::Block,
286        >,
287    ) -> Self::ExecutionData {
288        T::block_to_payload(block)
289    }
290}
291
292impl<T> EngineTypes for ArbEngineTypes<T>
293where
294    T: PayloadTypes<ExecutionData = ExecutionData>,
295    T::BuiltPayload: BuiltPayload<Primitives: NodePrimitives<Block = ArbBlock>>
296        + TryInto<ExecutionPayloadV1>
297        + TryInto<ExecutionPayloadEnvelopeV2>
298        + TryInto<ExecutionPayloadEnvelopeV3>
299        + TryInto<ExecutionPayloadEnvelopeV4>
300        + TryInto<ExecutionPayloadEnvelopeV5>
301        + TryInto<ExecutionPayloadEnvelopeV6>,
302{
303    type ExecutionPayloadEnvelopeV1 = ExecutionPayloadV1;
304    type ExecutionPayloadEnvelopeV2 = ExecutionPayloadEnvelopeV2;
305    type ExecutionPayloadEnvelopeV3 = ExecutionPayloadEnvelopeV3;
306    type ExecutionPayloadEnvelopeV4 = ExecutionPayloadEnvelopeV4;
307    type ExecutionPayloadEnvelopeV5 = ExecutionPayloadEnvelopeV5;
308    type ExecutionPayloadEnvelopeV6 = ExecutionPayloadEnvelopeV6;
309}