1use 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#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct BuiltPayloadExecutedBlock<N: NodePrimitives> {
25 pub recovered_block: Arc<RecoveredBlock<N::Block>>,
27 pub execution_output: Arc<BlockExecutionOutput<N::Receipt>>,
29 pub hashed_state: Either<Arc<HashedPostState>, Arc<HashedPostStateSorted>>,
34 pub trie_updates: Either<Arc<TrieUpdates>, Arc<TrieUpdatesSorted>>,
39}
40
41impl<N: NodePrimitives> BuiltPayloadExecutedBlock<N> {
42 pub fn into_executed_payload(self) -> reth_chain_state::ExecutedBlock<N> {
47 let hashed_state = match self.hashed_state {
48 Either::Left(unsorted) => Arc::new(Arc::unwrap_or_clone(unsorted).into_sorted()),
50 Either::Right(sorted) => sorted,
52 };
53
54 let trie_updates = match self.trie_updates {
55 Either::Left(unsorted) => Arc::new(Arc::unwrap_or_clone(unsorted).into_sorted()),
57 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#[auto_impl::auto_impl(&, Arc)]
74pub trait BuiltPayload: Send + Sync + fmt::Debug {
75 type Primitives: NodePrimitives;
77
78 fn block(&self) -> &SealedBlock<<Self::Primitives as NodePrimitives>::Block>;
80
81 fn fees(&self) -> U256;
83
84 fn executed_block(&self) -> Option<BuiltPayloadExecutedBlock<Self::Primitives>> {
88 None
89 }
90
91 fn requests(&self) -> Option<Requests>;
96}
97
98pub trait PayloadAttributes:
103 serde::de::DeserializeOwned + serde::Serialize + fmt::Debug + Clone + Send + Sync + 'static
104{
105 fn payload_id(&self, parent_hash: &B256) -> PayloadId;
107
108 fn timestamp(&self) -> u64;
110
111 fn withdrawals(&self) -> Option<&Vec<Withdrawal>>;
115
116 fn parent_beacon_block_root(&self) -> Option<B256>;
120
121 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
149pub trait PayloadAttributesBuilder<Attributes, Header = alloy_consensus::Header>:
154 Send + Sync + 'static
155{
156 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
194pub trait BuildNextEnv<Attributes, Header, Ctx>: Sized {
198 fn build_next_env(
200 attributes: &Attributes,
201 parent: &SealedHeader<Header>,
202 ctx: &Ctx,
203 ) -> Result<Self, PayloadBuilderError>;
204}
205
206pub 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)] 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 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 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 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 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 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 assert_eq!(
342 payload_id(&parent, &attributes),
343 PayloadId(B64::from_str("0x0fc49cd532094cce").unwrap())
344 );
345 }
346}