1use 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#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct BuiltPayloadExecutedBlock<N: NodePrimitives> {
21 pub recovered_block: Arc<RecoveredBlock<N::Block>>,
23 pub execution_output: Arc<BlockExecutionOutput<N::Receipt>>,
25 pub hashed_state: Arc<HashedPostState>,
27 pub trie_updates: Arc<TrieUpdates>,
29 pub changed_paths: Option<Arc<TriePrefixSetsMut>>,
31}
32
33#[auto_impl::auto_impl(&, Arc)]
38pub trait BuiltPayload: Send + Sync + fmt::Debug {
39 type Primitives: NodePrimitives;
41
42 fn block(&self) -> &SealedBlock<<Self::Primitives as NodePrimitives>::Block>;
44
45 fn fees(&self) -> U256;
47
48 fn block_access_list(&self) -> Option<&Bytes> {
52 None
53 }
54
55 fn executed_block(&self) -> Option<BuiltPayloadExecutedBlock<Self::Primitives>> {
59 None
60 }
61
62 fn requests(&self) -> Option<Requests>;
67}
68
69pub trait PayloadAttributes:
74 serde::de::DeserializeOwned + serde::Serialize + fmt::Debug + Clone + Send + Sync + 'static
75{
76 fn payload_id(&self, parent_hash: &B256) -> PayloadId;
78
79 fn timestamp(&self) -> u64;
81
82 fn withdrawals(&self) -> Option<&Vec<Withdrawal>>;
86
87 fn parent_beacon_block_root(&self) -> Option<B256>;
91
92 fn slot_number(&self) -> Option<u64>;
96
97 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
132pub trait PayloadAttributesBuilder<Attributes, Header = alloy_consensus::Header>:
137 Send + Sync + 'static
138{
139 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
177pub trait BuildNextEnv<Attributes, Header, Ctx>: Sized {
181 fn build_next_env(
183 attributes: &Attributes,
184 parent: &SealedHeader<Header>,
185 ctx: &Ctx,
186 ) -> Result<Self, PayloadBuilderError>;
187}
188
189pub 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)] 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 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 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 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 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 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 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}