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
104impl PayloadAttributes for EthPayloadAttributes {
105 fn payload_id(&self, parent_hash: &B256) -> PayloadId {
106 payload_id(parent_hash, self)
107 }
108
109 fn timestamp(&self) -> u64 {
110 self.timestamp
111 }
112
113 fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
114 self.withdrawals.as_ref()
115 }
116
117 fn parent_beacon_block_root(&self) -> Option<B256> {
118 self.parent_beacon_block_root
119 }
120
121 fn slot_number(&self) -> Option<u64> {
122 self.slot_number
123 }
124
125 fn target_gas_limit(&self) -> Option<u64> {
126 self.target_gas_limit
127 }
128}
129
130pub trait PayloadAttributesBuilder<Attributes, Header = alloy_consensus::Header>:
135 Send + Sync + 'static
136{
137 fn build(&self, parent: &SealedHeader<Header>) -> Attributes;
139}
140
141impl<Attributes, Header, F> PayloadAttributesBuilder<Attributes, Header> for F
142where
143 Header: Clone,
144 F: Fn(SealedHeader<Header>) -> Attributes + Send + Sync + 'static,
145{
146 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
147 self(parent.clone())
148 }
149}
150
151impl<Attributes, Header, L, R> PayloadAttributesBuilder<Attributes, Header> for Either<L, R>
152where
153 L: PayloadAttributesBuilder<Attributes, Header>,
154 R: PayloadAttributesBuilder<Attributes, Header>,
155{
156 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
157 match self {
158 Self::Left(l) => l.build(parent),
159 Self::Right(r) => r.build(parent),
160 }
161 }
162}
163
164impl<Attributes, Header> PayloadAttributesBuilder<Attributes, Header>
165 for Box<dyn PayloadAttributesBuilder<Attributes, Header>>
166where
167 Header: 'static,
168 Attributes: 'static,
169{
170 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
171 self.as_ref().build(parent)
172 }
173}
174
175pub trait BuildNextEnv<Attributes, Header, Ctx>: Sized {
179 fn build_next_env(
181 attributes: &Attributes,
182 parent: &SealedHeader<Header>,
183 ctx: &Ctx,
184 ) -> Result<Self, PayloadBuilderError>;
185}
186
187pub fn payload_id(
191 parent: &B256,
192 attributes: &alloy_rpc_types_engine::PayloadAttributes,
193) -> PayloadId {
194 use sha2::Digest;
195 let mut hasher = sha2::Sha256::new();
196 hasher.update(parent.as_slice());
197 hasher.update(&attributes.timestamp.to_be_bytes()[..]);
198 hasher.update(attributes.prev_randao.as_slice());
199 hasher.update(attributes.suggested_fee_recipient.as_slice());
200 if let Some(withdrawals) = &attributes.withdrawals {
201 let mut buf = Vec::new();
202 withdrawals.encode(&mut buf);
203 hasher.update(buf);
204 }
205
206 if let Some(parent_beacon_block) = attributes.parent_beacon_block_root {
207 hasher.update(parent_beacon_block);
208 }
209
210 if let Some(slot_number) = attributes.slot_number {
211 hasher.update(slot_number.to_be_bytes());
212 }
213
214 if let Some(target_gas_limit) = attributes.target_gas_limit {
215 hasher.update(target_gas_limit.to_be_bytes());
216 }
217
218 let out = hasher.finalize();
219
220 #[allow(deprecated)] PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227 use alloy_eips::eip4895::Withdrawal;
228 use alloy_primitives::{Address, B64};
229 use core::str::FromStr;
230
231 #[test]
232 fn attributes_serde() {
233 let attributes = r#"{"timestamp":"0x1235","prevRandao":"0xf343b00e02dc34ec0124241f74f32191be28fb370bb48060f5fa4df99bda774c","suggestedFeeRecipient":"0x0000000000000000000000000000000000000000","withdrawals":null,"parentBeaconBlockRoot":null}"#;
234 let _attributes: EthPayloadAttributes = serde_json::from_str(attributes).unwrap();
235 }
236
237 #[test]
238 fn test_payload_id_basic() {
239 let parent =
241 B256::from_str("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a")
242 .unwrap();
243 let attributes = EthPayloadAttributes {
244 timestamp: 0x5,
245 prev_randao: B256::from_str(
246 "0x0000000000000000000000000000000000000000000000000000000000000000",
247 )
248 .unwrap(),
249 suggested_fee_recipient: Address::from_str(
250 "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
251 )
252 .unwrap(),
253 withdrawals: None,
254 parent_beacon_block_root: None,
255 slot_number: None,
256 target_gas_limit: None,
257 };
258
259 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 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 slot_number: None,
295 target_gas_limit: None,
296 };
297
298 assert_eq!(
300 payload_id(&parent, &attributes),
301 PayloadId(B64::from_str("0xedddc2f84ba59865").unwrap())
302 );
303 }
304
305 #[test]
306 fn test_payload_id_with_parent_beacon_block_root() {
307 let parent =
309 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
310 .unwrap();
311 let attributes = EthPayloadAttributes {
312 timestamp: 1622553200,
313 prev_randao: B256::from_str(
314 "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
315 )
316 .unwrap(),
317 suggested_fee_recipient: Address::from_str(
318 "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
319 )
320 .unwrap(),
321 withdrawals: None,
322 parent_beacon_block_root: Some(
323 B256::from_str(
324 "0x2222222222222222222222222222222222222222222222222222222222222222",
325 )
326 .unwrap(),
327 ),
328 slot_number: None,
329 target_gas_limit: None,
330 };
331
332 assert_eq!(
334 payload_id(&parent, &attributes),
335 PayloadId(B64::from_str("0x0fc49cd532094cce").unwrap())
336 );
337 }
338
339 #[test]
340 fn test_payload_id_with_slot_number() {
341 let parent =
342 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
343 .unwrap();
344 let mut attributes = EthPayloadAttributes {
345 timestamp: 1622553200,
346 prev_randao: B256::from_slice(&[1; 32]),
347 suggested_fee_recipient: Address::from_str(
348 "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
349 )
350 .unwrap(),
351 withdrawals: Some(vec![]),
352 parent_beacon_block_root: Some(B256::from_slice(&[2; 32])),
353 slot_number: Some(1),
354 target_gas_limit: None,
355 };
356
357 let first = payload_id(&parent, &attributes);
358 attributes.slot_number = Some(2);
359
360 assert_ne!(first, payload_id(&parent, &attributes));
361 }
362
363 #[test]
364 fn test_payload_id_with_target_gas_limit() {
365 let parent =
366 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
367 .unwrap();
368 let mut attributes = EthPayloadAttributes {
369 timestamp: 1622553200,
370 prev_randao: B256::from_slice(&[1; 32]),
371 suggested_fee_recipient: Address::from_str(
372 "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
373 )
374 .unwrap(),
375 withdrawals: Some(vec![]),
376 parent_beacon_block_root: Some(B256::from_slice(&[2; 32])),
377 slot_number: Some(1),
378 target_gas_limit: Some(30_000_000),
379 };
380
381 let first = payload_id(&parent, &attributes);
382 attributes.target_gas_limit = Some(60_000_000);
383
384 assert_ne!(first, payload_id(&parent, &attributes));
385 }
386}