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