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::{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}
30
31#[auto_impl::auto_impl(&, Arc)]
36pub trait BuiltPayload: Send + Sync + fmt::Debug {
37 type Primitives: NodePrimitives;
39
40 fn block(&self) -> &SealedBlock<<Self::Primitives as NodePrimitives>::Block>;
42
43 fn fees(&self) -> U256;
45
46 fn block_access_list(&self) -> Option<&Bytes> {
50 None
51 }
52
53 fn executed_block(&self) -> Option<BuiltPayloadExecutedBlock<Self::Primitives>> {
57 None
58 }
59
60 fn requests(&self) -> Option<Requests>;
65}
66
67pub trait PayloadAttributes:
72 serde::de::DeserializeOwned + serde::Serialize + fmt::Debug + Clone + Send + Sync + 'static
73{
74 fn payload_id(&self, parent_hash: &B256) -> PayloadId;
76
77 fn timestamp(&self) -> u64;
79
80 fn withdrawals(&self) -> Option<&Vec<Withdrawal>>;
84
85 fn parent_beacon_block_root(&self) -> Option<B256>;
89
90 fn slot_number(&self) -> Option<u64>;
94
95 fn target_gas_limit(&self) -> Option<u64> {
100 None
101 }
102
103 fn inclusion_list_transactions(&self) -> Option<&[Bytes]> {
105 None
106 }
107}
108
109impl PayloadAttributes for EthPayloadAttributes {
110 fn payload_id(&self, parent_hash: &B256) -> PayloadId {
111 payload_id(parent_hash, self)
112 }
113
114 fn timestamp(&self) -> u64 {
115 self.timestamp
116 }
117
118 fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
119 self.withdrawals.as_ref()
120 }
121
122 fn parent_beacon_block_root(&self) -> Option<B256> {
123 self.parent_beacon_block_root
124 }
125
126 fn slot_number(&self) -> Option<u64> {
127 self.slot_number
128 }
129
130 fn target_gas_limit(&self) -> Option<u64> {
131 self.target_gas_limit
132 }
133}
134
135pub trait PayloadAttributesBuilder<Attributes, Header = alloy_consensus::Header>:
140 Send + Sync + 'static
141{
142 fn build(&self, parent: &SealedHeader<Header>) -> Attributes;
144}
145
146impl<Attributes, Header, F> PayloadAttributesBuilder<Attributes, Header> for F
147where
148 Header: Clone,
149 F: Fn(SealedHeader<Header>) -> Attributes + Send + Sync + 'static,
150{
151 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
152 self(parent.clone())
153 }
154}
155
156impl<Attributes, Header, L, R> PayloadAttributesBuilder<Attributes, Header> for Either<L, R>
157where
158 L: PayloadAttributesBuilder<Attributes, Header>,
159 R: PayloadAttributesBuilder<Attributes, Header>,
160{
161 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
162 match self {
163 Self::Left(l) => l.build(parent),
164 Self::Right(r) => r.build(parent),
165 }
166 }
167}
168
169impl<Attributes, Header> PayloadAttributesBuilder<Attributes, Header>
170 for Box<dyn PayloadAttributesBuilder<Attributes, Header>>
171where
172 Header: 'static,
173 Attributes: 'static,
174{
175 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
176 self.as_ref().build(parent)
177 }
178}
179
180pub trait BuildNextEnv<Attributes, Header, Ctx>: Sized {
184 fn build_next_env(
186 attributes: &Attributes,
187 parent: &SealedHeader<Header>,
188 ctx: &Ctx,
189 ) -> Result<Self, PayloadBuilderError>;
190}
191
192pub fn payload_id(
196 parent: &B256,
197 attributes: &alloy_rpc_types_engine::PayloadAttributes,
198) -> PayloadId {
199 use sha2::Digest;
200 let mut hasher = sha2::Sha256::new();
201 hasher.update(parent.as_slice());
202 hasher.update(&attributes.timestamp.to_be_bytes()[..]);
203 hasher.update(attributes.prev_randao.as_slice());
204 hasher.update(attributes.suggested_fee_recipient.as_slice());
205 if let Some(withdrawals) = &attributes.withdrawals {
206 let mut buf = Vec::new();
207 withdrawals.encode(&mut buf);
208 hasher.update(buf);
209 }
210
211 if let Some(parent_beacon_block) = attributes.parent_beacon_block_root {
212 hasher.update(parent_beacon_block);
213 }
214
215 if let Some(slot_number) = attributes.slot_number {
216 hasher.update(slot_number.to_be_bytes());
217 }
218
219 if let Some(target_gas_limit) = attributes.target_gas_limit {
220 hasher.update(target_gas_limit.to_be_bytes());
221 }
222
223 let out = hasher.finalize();
224
225 #[allow(deprecated)] PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use alloy_eips::eip4895::Withdrawal;
233 use alloy_primitives::{Address, B64};
234 use core::str::FromStr;
235
236 #[test]
237 fn attributes_serde() {
238 let attributes = r#"{"timestamp":"0x1235","prevRandao":"0xf343b00e02dc34ec0124241f74f32191be28fb370bb48060f5fa4df99bda774c","suggestedFeeRecipient":"0x0000000000000000000000000000000000000000","withdrawals":null,"parentBeaconBlockRoot":null}"#;
239 let _attributes: EthPayloadAttributes = serde_json::from_str(attributes).unwrap();
240 }
241
242 #[test]
243 fn test_payload_id_basic() {
244 let parent =
246 B256::from_str("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a")
247 .unwrap();
248 let attributes = EthPayloadAttributes {
249 timestamp: 0x5,
250 prev_randao: B256::from_str(
251 "0x0000000000000000000000000000000000000000000000000000000000000000",
252 )
253 .unwrap(),
254 suggested_fee_recipient: Address::from_str(
255 "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
256 )
257 .unwrap(),
258 withdrawals: None,
259 parent_beacon_block_root: None,
260 slot_number: None,
261 ..Default::default()
262 };
263
264 assert_eq!(
266 payload_id(&parent, &attributes),
267 PayloadId(B64::from_str("0xa247243752eb10b4").unwrap())
268 );
269 }
270
271 #[test]
272 fn test_payload_id_with_withdrawals() {
273 let parent =
275 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
276 .unwrap();
277 let attributes = EthPayloadAttributes {
278 timestamp: 1622553200,
279 prev_randao: B256::from_slice(&[1; 32]),
280 suggested_fee_recipient: Address::from_str(
281 "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
282 )
283 .unwrap(),
284 withdrawals: Some(vec![
285 Withdrawal {
286 index: 1,
287 validator_index: 123,
288 address: Address::from([0xAA; 20]),
289 amount: 10,
290 },
291 Withdrawal {
292 index: 2,
293 validator_index: 456,
294 address: Address::from([0xBB; 20]),
295 amount: 20,
296 },
297 ]),
298 parent_beacon_block_root: None,
299 slot_number: None,
300 ..Default::default()
301 };
302
303 assert_eq!(
305 payload_id(&parent, &attributes),
306 PayloadId(B64::from_str("0xedddc2f84ba59865").unwrap())
307 );
308 }
309
310 #[test]
311 fn test_payload_id_with_parent_beacon_block_root() {
312 let parent =
314 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
315 .unwrap();
316 let attributes = EthPayloadAttributes {
317 timestamp: 1622553200,
318 prev_randao: B256::from_str(
319 "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
320 )
321 .unwrap(),
322 suggested_fee_recipient: Address::from_str(
323 "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
324 )
325 .unwrap(),
326 withdrawals: None,
327 parent_beacon_block_root: Some(
328 B256::from_str(
329 "0x2222222222222222222222222222222222222222222222222222222222222222",
330 )
331 .unwrap(),
332 ),
333 slot_number: None,
334 ..Default::default()
335 };
336
337 assert_eq!(
339 payload_id(&parent, &attributes),
340 PayloadId(B64::from_str("0x0fc49cd532094cce").unwrap())
341 );
342 }
343
344 #[test]
345 fn test_payload_id_with_slot_number() {
346 let parent =
347 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
348 .unwrap();
349 let mut attributes = EthPayloadAttributes {
350 timestamp: 1622553200,
351 prev_randao: B256::from_slice(&[1; 32]),
352 suggested_fee_recipient: Address::from_str(
353 "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
354 )
355 .unwrap(),
356 withdrawals: Some(vec![]),
357 parent_beacon_block_root: Some(B256::from_slice(&[2; 32])),
358 slot_number: Some(1),
359 ..Default::default()
360 };
361
362 let first = payload_id(&parent, &attributes);
363 attributes.slot_number = Some(2);
364
365 assert_ne!(first, payload_id(&parent, &attributes));
366 }
367
368 #[test]
369 fn test_payload_id_with_target_gas_limit() {
370 let parent =
371 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
372 .unwrap();
373 #[allow(clippy::needless_update)]
374 let mut attributes = EthPayloadAttributes {
375 timestamp: 1622553200,
376 prev_randao: B256::from_slice(&[1; 32]),
377 suggested_fee_recipient: Address::from_str(
378 "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
379 )
380 .unwrap(),
381 withdrawals: Some(vec![]),
382 parent_beacon_block_root: Some(B256::from_slice(&[2; 32])),
383 slot_number: Some(1),
384 target_gas_limit: Some(30_000_000),
385 ..Default::default()
386 };
387
388 let first = payload_id(&parent, &attributes);
389 attributes.target_gas_limit = Some(60_000_000);
390
391 assert_ne!(first, payload_id(&parent, &attributes));
392 }
393}