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_rpc_types_engine::{PayloadAttributes as EthPayloadAttributes, PayloadId};
8use core::fmt;
9use either::Either;
10use reth_execution_types::BlockExecutionOutput;
11use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader};
12use reth_trie_common::{updates::TrieUpdates, HashedPostState};
13
14#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct BuiltPayloadExecutedBlock<N: NodePrimitives> {
20 pub recovered_block: Arc<RecoveredBlock<N::Block>>,
22 pub execution_output: Arc<BlockExecutionOutput<N::Receipt>>,
24 pub hashed_state: Arc<HashedPostState>,
26 pub trie_updates: Arc<TrieUpdates>,
28}
29
30#[auto_impl::auto_impl(&, Arc)]
35pub trait BuiltPayload: Send + Sync + fmt::Debug {
36 type Primitives: NodePrimitives;
38
39 fn block(&self) -> &SealedBlock<<Self::Primitives as NodePrimitives>::Block>;
41
42 fn fees(&self) -> U256;
44
45 fn block_access_list(&self) -> Option<&Bytes> {
49 None
50 }
51
52 fn executed_block(&self) -> Option<BuiltPayloadExecutedBlock<Self::Primitives>> {
56 None
57 }
58
59 fn requests(&self) -> Option<Requests>;
64}
65
66pub trait PayloadAttributes:
71 serde::de::DeserializeOwned + serde::Serialize + fmt::Debug + Clone + Send + Sync + 'static
72{
73 fn payload_id(&self, parent_hash: &B256) -> PayloadId;
75
76 fn timestamp(&self) -> u64;
78
79 fn withdrawals(&self) -> Option<&Vec<Withdrawal>>;
83
84 fn parent_beacon_block_root(&self) -> Option<B256>;
88
89 fn slot_number(&self) -> Option<u64>;
93
94 fn target_gas_limit(&self) -> Option<u64> {
99 None
100 }
101
102 fn inclusion_list_transactions(&self) -> Option<&[Bytes]> {
104 None
105 }
106}
107
108impl PayloadAttributes for EthPayloadAttributes {
109 fn payload_id(&self, parent_hash: &B256) -> PayloadId {
110 payload_id(parent_hash, self)
111 }
112
113 fn timestamp(&self) -> u64 {
114 self.timestamp
115 }
116
117 fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
118 self.withdrawals.as_ref()
119 }
120
121 fn parent_beacon_block_root(&self) -> Option<B256> {
122 self.parent_beacon_block_root
123 }
124
125 fn slot_number(&self) -> Option<u64> {
126 self.slot_number
127 }
128
129 fn target_gas_limit(&self) -> Option<u64> {
130 self.target_gas_limit
131 }
132}
133
134pub trait PayloadAttributesBuilder<Attributes, Header = alloy_consensus::Header>:
139 Send + Sync + 'static
140{
141 fn build(&self, parent: &SealedHeader<Header>) -> Attributes;
143}
144
145impl<Attributes, Header, F> PayloadAttributesBuilder<Attributes, Header> for F
146where
147 Header: Clone,
148 F: Fn(SealedHeader<Header>) -> Attributes + Send + Sync + 'static,
149{
150 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
151 self(parent.clone())
152 }
153}
154
155impl<Attributes, Header, L, R> PayloadAttributesBuilder<Attributes, Header> for Either<L, R>
156where
157 L: PayloadAttributesBuilder<Attributes, Header>,
158 R: PayloadAttributesBuilder<Attributes, Header>,
159{
160 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
161 match self {
162 Self::Left(l) => l.build(parent),
163 Self::Right(r) => r.build(parent),
164 }
165 }
166}
167
168impl<Attributes, Header> PayloadAttributesBuilder<Attributes, Header>
169 for Box<dyn PayloadAttributesBuilder<Attributes, Header>>
170where
171 Header: 'static,
172 Attributes: 'static,
173{
174 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
175 self.as_ref().build(parent)
176 }
177}
178
179pub trait BuildNextEnv<Attributes, Header, Ctx>: Sized {
183 fn build_next_env(
185 attributes: &Attributes,
186 parent: &SealedHeader<Header>,
187 ctx: &Ctx,
188 ) -> Result<Self, PayloadBuilderError>;
189}
190
191pub fn payload_id(
195 parent: &B256,
196 attributes: &alloy_rpc_types_engine::PayloadAttributes,
197) -> PayloadId {
198 use sha2::Digest;
199 let mut hasher = sha2::Sha256::new();
200 hasher.update(parent.as_slice());
201 hasher.update(&attributes.timestamp.to_be_bytes()[..]);
202 hasher.update(attributes.prev_randao.as_slice());
203 hasher.update(attributes.suggested_fee_recipient.as_slice());
204 if let Some(withdrawals) = &attributes.withdrawals {
205 hasher.update(alloy_rlp::encode(withdrawals));
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, b256, b64, Address};
231
232 #[test]
233 fn attributes_serde() {
234 let attributes = r#"{"timestamp":"0x1235","prevRandao":"0xf343b00e02dc34ec0124241f74f32191be28fb370bb48060f5fa4df99bda774c","suggestedFeeRecipient":"0x0000000000000000000000000000000000000000","withdrawals":null,"parentBeaconBlockRoot":null}"#;
235 let _attributes: EthPayloadAttributes = serde_json::from_str(attributes).unwrap();
236 }
237
238 #[test]
239 fn test_payload_id_basic() {
240 let parent = b256!("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a");
242 let attributes = EthPayloadAttributes {
243 timestamp: 0x5,
244 prev_randao: B256::ZERO,
245 suggested_fee_recipient: address!("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"),
246 withdrawals: None,
247 parent_beacon_block_root: None,
248 slot_number: None,
249 ..Default::default()
250 };
251
252 assert_eq!(payload_id(&parent, &attributes), PayloadId(b64!("0xa247243752eb10b4")));
254 }
255
256 #[test]
257 fn test_payload_id_with_withdrawals() {
258 let parent = b256!("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef");
260 let attributes = EthPayloadAttributes {
261 timestamp: 1622553200,
262 prev_randao: B256::from_slice(&[1; 32]),
263 suggested_fee_recipient: address!("0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b"),
264 withdrawals: Some(vec![
265 Withdrawal {
266 index: 1,
267 validator_index: 123,
268 address: Address::repeat_byte(0xAA),
269 amount: 10,
270 },
271 Withdrawal {
272 index: 2,
273 validator_index: 456,
274 address: Address::repeat_byte(0xBB),
275 amount: 20,
276 },
277 ]),
278 parent_beacon_block_root: None,
279 slot_number: None,
280 ..Default::default()
281 };
282
283 assert_eq!(payload_id(&parent, &attributes), PayloadId(b64!("0xedddc2f84ba59865")));
285 }
286
287 #[test]
288 fn test_payload_id_with_parent_beacon_block_root() {
289 let parent = b256!("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef");
291 let attributes = EthPayloadAttributes {
292 timestamp: 1622553200,
293 prev_randao: b256!(
294 "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234"
295 ),
296 suggested_fee_recipient: address!("0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b"),
297 withdrawals: None,
298 parent_beacon_block_root: Some(B256::repeat_byte(0x22)),
299 slot_number: None,
300 ..Default::default()
301 };
302
303 assert_eq!(payload_id(&parent, &attributes), PayloadId(b64!("0x0fc49cd532094cce")));
305 }
306
307 #[test]
308 fn test_payload_id_with_slot_number() {
309 let parent = b256!("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef");
310 let mut attributes = EthPayloadAttributes {
311 timestamp: 1622553200,
312 prev_randao: B256::from_slice(&[1; 32]),
313 suggested_fee_recipient: address!("0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b"),
314 withdrawals: Some(vec![]),
315 parent_beacon_block_root: Some(B256::from_slice(&[2; 32])),
316 slot_number: Some(1),
317 ..Default::default()
318 };
319
320 let first = payload_id(&parent, &attributes);
321 attributes.slot_number = Some(2);
322
323 assert_ne!(first, payload_id(&parent, &attributes));
324 }
325
326 #[test]
327 fn test_payload_id_with_target_gas_limit() {
328 let parent = b256!("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef");
329 #[allow(clippy::needless_update)]
330 let mut attributes = EthPayloadAttributes {
331 timestamp: 1622553200,
332 prev_randao: B256::from_slice(&[1; 32]),
333 suggested_fee_recipient: address!("0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b"),
334 withdrawals: Some(vec![]),
335 parent_beacon_block_root: Some(B256::from_slice(&[2; 32])),
336 slot_number: Some(1),
337 target_gas_limit: Some(30_000_000),
338 ..Default::default()
339 };
340
341 let first = payload_id(&parent, &attributes);
342 attributes.target_gas_limit = Some(60_000_000);
343
344 assert_ne!(first, payload_id(&parent, &attributes));
345 }
346}