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