Skip to main content

reth_payload_primitives/
traits.rs

1//! Core traits for working with execution payloads.
2
3use crate::PayloadBuilderError;
4use alloc::{boxed::Box, sync::Arc, vec::Vec};
5use alloy_eips::{eip4895::Withdrawal, eip7685::Requests};
6use alloy_primitives::{Bytes, B256, U256};
7use alloy_rlp::Encodable;
8use alloy_rpc_types_engine::{PayloadAttributes as EthPayloadAttributes, PayloadId};
9use core::fmt;
10use either::Either;
11use reth_execution_types::BlockExecutionOutput;
12use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader};
13use reth_trie_common::{prefix_set::TriePrefixSetsMut, updates::TrieUpdates, HashedPostState};
14
15/// Represents an executed block for payload building purposes.
16///
17/// This type captures the complete execution state of a built block,
18/// including the recovered block, execution outcome, hashed state, and trie updates.
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct BuiltPayloadExecutedBlock<N: NodePrimitives> {
21    /// Recovered Block
22    pub recovered_block: Arc<RecoveredBlock<N::Block>>,
23    /// Block's execution outcome.
24    pub execution_output: Arc<BlockExecutionOutput<N::Receipt>>,
25    /// Block's hashed state (unsorted).
26    pub hashed_state: Arc<HashedPostState>,
27    /// Trie updates that result from calculating the state root for the block (unsorted).
28    pub trie_updates: Arc<TrieUpdates>,
29    /// Changed trie node base paths, if known.
30    pub changed_paths: Option<Arc<TriePrefixSetsMut>>,
31}
32
33/// Represents a successfully built execution payload (block).
34///
35/// Provides access to the underlying block data, execution results, and associated metadata
36/// for payloads ready for execution or propagation.
37#[auto_impl::auto_impl(&, Arc)]
38pub trait BuiltPayload: Send + Sync + fmt::Debug {
39    /// The node's primitive types
40    type Primitives: NodePrimitives;
41
42    /// Returns the built block in its sealed (hash-verified) form.
43    fn block(&self) -> &SealedBlock<<Self::Primitives as NodePrimitives>::Block>;
44
45    /// Returns the total fees collected from all transactions in this block.
46    fn fees(&self) -> U256;
47
48    /// Returns the EIP-7928 block access list included in this payload.
49    ///
50    /// Returns `None` for payloads that do not carry a block access list.
51    fn block_access_list(&self) -> Option<&Bytes> {
52        None
53    }
54
55    /// Returns the complete execution result including state updates.
56    ///
57    /// Returns `None` if execution data is not available or not tracked.
58    fn executed_block(&self) -> Option<BuiltPayloadExecutedBlock<Self::Primitives>> {
59        None
60    }
61
62    /// Returns the EIP-7685 execution layer requests included in this block.
63    ///
64    /// These are requests generated by the execution layer that need to be
65    /// processed by the consensus layer (e.g., validator deposits, withdrawals).
66    fn requests(&self) -> Option<Requests>;
67}
68
69/// Basic attributes required to initiate payload construction.
70///
71/// Defines minimal parameters needed to build a new execution payload.
72/// Implementations must be serializable for transmission.
73pub trait PayloadAttributes:
74    serde::de::DeserializeOwned + serde::Serialize + fmt::Debug + Clone + Send + Sync + 'static
75{
76    /// Computes the unique identifier for this payload build job.
77    fn payload_id(&self, parent_hash: &B256) -> PayloadId;
78
79    /// Returns the timestamp for the new payload.
80    fn timestamp(&self) -> u64;
81
82    /// Returns the withdrawals to be included in the payload.
83    ///
84    /// `Some` for post-Shanghai blocks, `None` for earlier blocks.
85    fn withdrawals(&self) -> Option<&Vec<Withdrawal>>;
86
87    /// Returns the parent beacon block root.
88    ///
89    /// `Some` for post-merge blocks, `None` for pre-merge blocks.
90    fn parent_beacon_block_root(&self) -> Option<B256>;
91
92    /// Returns the slot number for the new payload.
93    ///
94    /// `Some` for post-Amsterdam blocks, `None` for earlier blocks.
95    fn slot_number(&self) -> Option<u64>;
96
97    /// Returns the target gas limit for the new payload.
98    ///
99    /// `Some` for payload attributes that specify the desired gas limit, `None` if the builder
100    /// should use its configured target.
101    fn target_gas_limit(&self) -> Option<u64> {
102        None
103    }
104}
105
106impl PayloadAttributes for EthPayloadAttributes {
107    fn payload_id(&self, parent_hash: &B256) -> PayloadId {
108        payload_id(parent_hash, self)
109    }
110
111    fn timestamp(&self) -> u64 {
112        self.timestamp
113    }
114
115    fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
116        self.withdrawals.as_ref()
117    }
118
119    fn parent_beacon_block_root(&self) -> Option<B256> {
120        self.parent_beacon_block_root
121    }
122
123    fn slot_number(&self) -> Option<u64> {
124        self.slot_number
125    }
126
127    fn target_gas_limit(&self) -> Option<u64> {
128        self.target_gas_limit
129    }
130}
131
132/// Factory trait for creating payload attributes.
133///
134/// Enables different strategies for generating payload attributes based on
135/// contextual information. Useful for testing and specialized building.
136pub trait PayloadAttributesBuilder<Attributes, Header = alloy_consensus::Header>:
137    Send + Sync + 'static
138{
139    /// Constructs new payload attributes for the given timestamp.
140    fn build(&self, parent: &SealedHeader<Header>) -> Attributes;
141}
142
143impl<Attributes, Header, F> PayloadAttributesBuilder<Attributes, Header> for F
144where
145    Header: Clone,
146    F: Fn(SealedHeader<Header>) -> Attributes + Send + Sync + 'static,
147{
148    fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
149        self(parent.clone())
150    }
151}
152
153impl<Attributes, Header, L, R> PayloadAttributesBuilder<Attributes, Header> for Either<L, R>
154where
155    L: PayloadAttributesBuilder<Attributes, Header>,
156    R: PayloadAttributesBuilder<Attributes, Header>,
157{
158    fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
159        match self {
160            Self::Left(l) => l.build(parent),
161            Self::Right(r) => r.build(parent),
162        }
163    }
164}
165
166impl<Attributes, Header> PayloadAttributesBuilder<Attributes, Header>
167    for Box<dyn PayloadAttributesBuilder<Attributes, Header>>
168where
169    Header: 'static,
170    Attributes: 'static,
171{
172    fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
173        self.as_ref().build(parent)
174    }
175}
176
177/// Trait to build the EVM environment for the next block from the given payload attributes.
178///
179/// Accepts payload attributes from CL, parent header and additional payload builder context.
180pub trait BuildNextEnv<Attributes, Header, Ctx>: Sized {
181    /// Builds the EVM environment for the next block from the given payload attributes.
182    fn build_next_env(
183        attributes: &Attributes,
184        parent: &SealedHeader<Header>,
185        ctx: &Ctx,
186    ) -> Result<Self, PayloadBuilderError>;
187}
188
189/// Generates the payload id for the configured payload from the [`PayloadAttributes`].
190///
191/// Returns an 8-byte identifier by hashing the payload components with sha256 hash.
192pub fn payload_id(
193    parent: &B256,
194    attributes: &alloy_rpc_types_engine::PayloadAttributes,
195) -> PayloadId {
196    use sha2::Digest;
197    let mut hasher = sha2::Sha256::new();
198    hasher.update(parent.as_slice());
199    hasher.update(&attributes.timestamp.to_be_bytes()[..]);
200    hasher.update(attributes.prev_randao.as_slice());
201    hasher.update(attributes.suggested_fee_recipient.as_slice());
202    if let Some(withdrawals) = &attributes.withdrawals {
203        let mut buf = Vec::new();
204        withdrawals.encode(&mut buf);
205        hasher.update(buf);
206    }
207
208    if let Some(parent_beacon_block) = attributes.parent_beacon_block_root {
209        hasher.update(parent_beacon_block);
210    }
211
212    if let Some(slot_number) = attributes.slot_number {
213        hasher.update(slot_number.to_be_bytes());
214    }
215
216    if let Some(target_gas_limit) = attributes.target_gas_limit {
217        hasher.update(target_gas_limit.to_be_bytes());
218    }
219
220    let out = hasher.finalize();
221
222    #[allow(deprecated)] // generic-array 0.14 deprecated
223    PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229    use alloy_eips::eip4895::Withdrawal;
230    use alloy_primitives::{Address, B64};
231    use core::str::FromStr;
232
233    #[test]
234    fn attributes_serde() {
235        let attributes = r#"{"timestamp":"0x1235","prevRandao":"0xf343b00e02dc34ec0124241f74f32191be28fb370bb48060f5fa4df99bda774c","suggestedFeeRecipient":"0x0000000000000000000000000000000000000000","withdrawals":null,"parentBeaconBlockRoot":null}"#;
236        let _attributes: EthPayloadAttributes = serde_json::from_str(attributes).unwrap();
237    }
238
239    #[test]
240    fn test_payload_id_basic() {
241        // Create a parent block and payload attributes
242        let parent =
243            B256::from_str("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a")
244                .unwrap();
245        let attributes = EthPayloadAttributes {
246            timestamp: 0x5,
247            prev_randao: B256::from_str(
248                "0x0000000000000000000000000000000000000000000000000000000000000000",
249            )
250            .unwrap(),
251            suggested_fee_recipient: Address::from_str(
252                "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
253            )
254            .unwrap(),
255            withdrawals: None,
256            parent_beacon_block_root: None,
257            slot_number: None,
258            target_gas_limit: None,
259        };
260
261        // Verify that the generated payload ID matches the expected value
262        assert_eq!(
263            payload_id(&parent, &attributes),
264            PayloadId(B64::from_str("0xa247243752eb10b4").unwrap())
265        );
266    }
267
268    #[test]
269    fn test_payload_id_with_withdrawals() {
270        // Set up the parent and attributes with withdrawals
271        let parent =
272            B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
273                .unwrap();
274        let attributes = EthPayloadAttributes {
275            timestamp: 1622553200,
276            prev_randao: B256::from_slice(&[1; 32]),
277            suggested_fee_recipient: Address::from_str(
278                "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
279            )
280            .unwrap(),
281            withdrawals: Some(vec![
282                Withdrawal {
283                    index: 1,
284                    validator_index: 123,
285                    address: Address::from([0xAA; 20]),
286                    amount: 10,
287                },
288                Withdrawal {
289                    index: 2,
290                    validator_index: 456,
291                    address: Address::from([0xBB; 20]),
292                    amount: 20,
293                },
294            ]),
295            parent_beacon_block_root: None,
296            slot_number: None,
297            target_gas_limit: None,
298        };
299
300        // Verify that the generated payload ID matches the expected value
301        assert_eq!(
302            payload_id(&parent, &attributes),
303            PayloadId(B64::from_str("0xedddc2f84ba59865").unwrap())
304        );
305    }
306
307    #[test]
308    fn test_payload_id_with_parent_beacon_block_root() {
309        // Set up the parent and attributes with a parent beacon block root
310        let parent =
311            B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
312                .unwrap();
313        let attributes = EthPayloadAttributes {
314            timestamp: 1622553200,
315            prev_randao: B256::from_str(
316                "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
317            )
318            .unwrap(),
319            suggested_fee_recipient: Address::from_str(
320                "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
321            )
322            .unwrap(),
323            withdrawals: None,
324            parent_beacon_block_root: Some(
325                B256::from_str(
326                    "0x2222222222222222222222222222222222222222222222222222222222222222",
327                )
328                .unwrap(),
329            ),
330            slot_number: None,
331            target_gas_limit: None,
332        };
333
334        // Verify that the generated payload ID matches the expected value
335        assert_eq!(
336            payload_id(&parent, &attributes),
337            PayloadId(B64::from_str("0x0fc49cd532094cce").unwrap())
338        );
339    }
340
341    #[test]
342    fn test_payload_id_with_slot_number() {
343        let parent =
344            B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
345                .unwrap();
346        let mut attributes = EthPayloadAttributes {
347            timestamp: 1622553200,
348            prev_randao: B256::from_slice(&[1; 32]),
349            suggested_fee_recipient: Address::from_str(
350                "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
351            )
352            .unwrap(),
353            withdrawals: Some(vec![]),
354            parent_beacon_block_root: Some(B256::from_slice(&[2; 32])),
355            slot_number: Some(1),
356            target_gas_limit: None,
357        };
358
359        let first = payload_id(&parent, &attributes);
360        attributes.slot_number = Some(2);
361
362        assert_ne!(first, payload_id(&parent, &attributes));
363    }
364
365    #[test]
366    fn test_payload_id_with_target_gas_limit() {
367        let parent =
368            B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
369                .unwrap();
370        let mut attributes = EthPayloadAttributes {
371            timestamp: 1622553200,
372            prev_randao: B256::from_slice(&[1; 32]),
373            suggested_fee_recipient: Address::from_str(
374                "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
375            )
376            .unwrap(),
377            withdrawals: Some(vec![]),
378            parent_beacon_block_root: Some(B256::from_slice(&[2; 32])),
379            slot_number: Some(1),
380            target_gas_limit: Some(30_000_000),
381        };
382
383        let first = payload_id(&parent, &attributes);
384        attributes.target_gas_limit = Some(60_000_000);
385
386        assert_ne!(first, payload_id(&parent, &attributes));
387    }
388}