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
122impl PayloadAttributes for EthPayloadAttributes {
123    fn payload_id(&self, parent_hash: &B256) -> PayloadId {
124        payload_id(parent_hash, self)
125    }
126
127    fn timestamp(&self) -> u64 {
128        self.timestamp
129    }
130
131    fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
132        self.withdrawals.as_ref()
133    }
134
135    fn parent_beacon_block_root(&self) -> Option<B256> {
136        self.parent_beacon_block_root
137    }
138}
139
140/// Factory trait for creating payload attributes.
141///
142/// Enables different strategies for generating payload attributes based on
143/// contextual information. Useful for testing and specialized building.
144pub trait PayloadAttributesBuilder<Attributes, Header = alloy_consensus::Header>:
145    Send + Sync + 'static
146{
147    /// Constructs new payload attributes for the given timestamp.
148    fn build(&self, parent: &SealedHeader<Header>) -> Attributes;
149}
150
151impl<Attributes, Header, F> PayloadAttributesBuilder<Attributes, Header> for F
152where
153    Header: Clone,
154    F: Fn(SealedHeader<Header>) -> Attributes + Send + Sync + 'static,
155{
156    fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
157        self(parent.clone())
158    }
159}
160
161impl<Attributes, Header, L, R> PayloadAttributesBuilder<Attributes, Header> for Either<L, R>
162where
163    L: PayloadAttributesBuilder<Attributes, Header>,
164    R: PayloadAttributesBuilder<Attributes, Header>,
165{
166    fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
167        match self {
168            Self::Left(l) => l.build(parent),
169            Self::Right(r) => r.build(parent),
170        }
171    }
172}
173
174impl<Attributes, Header> PayloadAttributesBuilder<Attributes, Header>
175    for Box<dyn PayloadAttributesBuilder<Attributes, Header>>
176where
177    Header: 'static,
178    Attributes: 'static,
179{
180    fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
181        self.as_ref().build(parent)
182    }
183}
184
185/// Trait to build the EVM environment for the next block from the given payload attributes.
186///
187/// Accepts payload attributes from CL, parent header and additional payload builder context.
188pub trait BuildNextEnv<Attributes, Header, Ctx>: Sized {
189    /// Builds the EVM environment for the next block from the given payload attributes.
190    fn build_next_env(
191        attributes: &Attributes,
192        parent: &SealedHeader<Header>,
193        ctx: &Ctx,
194    ) -> Result<Self, PayloadBuilderError>;
195}
196
197/// Generates the payload id for the configured payload from the [`PayloadAttributes`].
198///
199/// Returns an 8-byte identifier by hashing the payload components with sha256 hash.
200pub fn payload_id(
201    parent: &B256,
202    attributes: &alloy_rpc_types_engine::PayloadAttributes,
203) -> PayloadId {
204    use sha2::Digest;
205    let mut hasher = sha2::Sha256::new();
206    hasher.update(parent.as_slice());
207    hasher.update(&attributes.timestamp.to_be_bytes()[..]);
208    hasher.update(attributes.prev_randao.as_slice());
209    hasher.update(attributes.suggested_fee_recipient.as_slice());
210    if let Some(withdrawals) = &attributes.withdrawals {
211        let mut buf = Vec::new();
212        withdrawals.encode(&mut buf);
213        hasher.update(buf);
214    }
215
216    if let Some(parent_beacon_block) = attributes.parent_beacon_block_root {
217        hasher.update(parent_beacon_block);
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        };
258
259        // Verify that the generated payload ID matches the expected value
260        assert_eq!(
261            payload_id(&parent, &attributes),
262            PayloadId(B64::from_str("0xa247243752eb10b4").unwrap())
263        );
264    }
265
266    #[test]
267    fn test_payload_id_with_withdrawals() {
268        // Set up the parent and attributes with withdrawals
269        let parent =
270            B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
271                .unwrap();
272        let attributes = EthPayloadAttributes {
273            timestamp: 1622553200,
274            prev_randao: B256::from_slice(&[1; 32]),
275            suggested_fee_recipient: Address::from_str(
276                "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
277            )
278            .unwrap(),
279            withdrawals: Some(vec![
280                Withdrawal {
281                    index: 1,
282                    validator_index: 123,
283                    address: Address::from([0xAA; 20]),
284                    amount: 10,
285                },
286                Withdrawal {
287                    index: 2,
288                    validator_index: 456,
289                    address: Address::from([0xBB; 20]),
290                    amount: 20,
291                },
292            ]),
293            parent_beacon_block_root: None,
294        };
295
296        // Verify that the generated payload ID matches the expected value
297        assert_eq!(
298            payload_id(&parent, &attributes),
299            PayloadId(B64::from_str("0xedddc2f84ba59865").unwrap())
300        );
301    }
302
303    #[test]
304    fn test_payload_id_with_parent_beacon_block_root() {
305        // Set up the parent and attributes with a parent beacon block root
306        let parent =
307            B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
308                .unwrap();
309        let attributes = EthPayloadAttributes {
310            timestamp: 1622553200,
311            prev_randao: B256::from_str(
312                "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
313            )
314            .unwrap(),
315            suggested_fee_recipient: Address::from_str(
316                "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
317            )
318            .unwrap(),
319            withdrawals: None,
320            parent_beacon_block_root: Some(
321                B256::from_str(
322                    "0x2222222222222222222222222222222222222222222222222222222222222222",
323                )
324                .unwrap(),
325            ),
326        };
327
328        // Verify that the generated payload ID matches the expected value
329        assert_eq!(
330            payload_id(&parent, &attributes),
331            PayloadId(B64::from_str("0x0fc49cd532094cce").unwrap())
332        );
333    }
334}