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
122impl PayloadAttributes for EthPayloadAttributes {
123 fn payload_id(&self, parent_hash: &B256) -> PayloadId {
124 payload_id(parent_hash, self)
125 }
126
127 fn timestamp(&self) -> u64 {
128 self.timestamp
129 }
130
131 fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
132 self.withdrawals.as_ref()
133 }
134
135 fn parent_beacon_block_root(&self) -> Option<B256> {
136 self.parent_beacon_block_root
137 }
138}
139
140pub trait PayloadAttributesBuilder<Attributes, Header = alloy_consensus::Header>:
145 Send + Sync + 'static
146{
147 fn build(&self, parent: &SealedHeader<Header>) -> Attributes;
149}
150
151impl<Attributes, Header, F> PayloadAttributesBuilder<Attributes, Header> for F
152where
153 Header: Clone,
154 F: Fn(SealedHeader<Header>) -> Attributes + Send + Sync + 'static,
155{
156 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
157 self(parent.clone())
158 }
159}
160
161impl<Attributes, Header, L, R> PayloadAttributesBuilder<Attributes, Header> for Either<L, R>
162where
163 L: PayloadAttributesBuilder<Attributes, Header>,
164 R: PayloadAttributesBuilder<Attributes, Header>,
165{
166 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
167 match self {
168 Self::Left(l) => l.build(parent),
169 Self::Right(r) => r.build(parent),
170 }
171 }
172}
173
174impl<Attributes, Header> PayloadAttributesBuilder<Attributes, Header>
175 for Box<dyn PayloadAttributesBuilder<Attributes, Header>>
176where
177 Header: 'static,
178 Attributes: 'static,
179{
180 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
181 self.as_ref().build(parent)
182 }
183}
184
185pub trait BuildNextEnv<Attributes, Header, Ctx>: Sized {
189 fn build_next_env(
191 attributes: &Attributes,
192 parent: &SealedHeader<Header>,
193 ctx: &Ctx,
194 ) -> Result<Self, PayloadBuilderError>;
195}
196
197pub fn payload_id(
201 parent: &B256,
202 attributes: &alloy_rpc_types_engine::PayloadAttributes,
203) -> PayloadId {
204 use sha2::Digest;
205 let mut hasher = sha2::Sha256::new();
206 hasher.update(parent.as_slice());
207 hasher.update(&attributes.timestamp.to_be_bytes()[..]);
208 hasher.update(attributes.prev_randao.as_slice());
209 hasher.update(attributes.suggested_fee_recipient.as_slice());
210 if let Some(withdrawals) = &attributes.withdrawals {
211 let mut buf = Vec::new();
212 withdrawals.encode(&mut buf);
213 hasher.update(buf);
214 }
215
216 if let Some(parent_beacon_block) = attributes.parent_beacon_block_root {
217 hasher.update(parent_beacon_block);
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 };
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 };
295
296 assert_eq!(
298 payload_id(&parent, &attributes),
299 PayloadId(B64::from_str("0xedddc2f84ba59865").unwrap())
300 );
301 }
302
303 #[test]
304 fn test_payload_id_with_parent_beacon_block_root() {
305 let parent =
307 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
308 .unwrap();
309 let attributes = EthPayloadAttributes {
310 timestamp: 1622553200,
311 prev_randao: B256::from_str(
312 "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
313 )
314 .unwrap(),
315 suggested_fee_recipient: Address::from_str(
316 "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
317 )
318 .unwrap(),
319 withdrawals: None,
320 parent_beacon_block_root: Some(
321 B256::from_str(
322 "0x2222222222222222222222222222222222222222222222222222222222222222",
323 )
324 .unwrap(),
325 ),
326 };
327
328 assert_eq!(
330 payload_id(&parent, &attributes),
331 PayloadId(B64::from_str("0x0fc49cd532094cce").unwrap())
332 );
333 }
334}