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