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
96impl PayloadAttributes for EthPayloadAttributes {
97 fn payload_id(&self, parent_hash: &B256) -> PayloadId {
98 payload_id(parent_hash, self)
99 }
100
101 fn timestamp(&self) -> u64 {
102 self.timestamp
103 }
104
105 fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
106 self.withdrawals.as_ref()
107 }
108
109 fn parent_beacon_block_root(&self) -> Option<B256> {
110 self.parent_beacon_block_root
111 }
112
113 fn slot_number(&self) -> Option<u64> {
114 self.slot_number
115 }
116}
117
118pub trait PayloadAttributesBuilder<Attributes, Header = alloy_consensus::Header>:
123 Send + Sync + 'static
124{
125 fn build(&self, parent: &SealedHeader<Header>) -> Attributes;
127}
128
129impl<Attributes, Header, F> PayloadAttributesBuilder<Attributes, Header> for F
130where
131 Header: Clone,
132 F: Fn(SealedHeader<Header>) -> Attributes + Send + Sync + 'static,
133{
134 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
135 self(parent.clone())
136 }
137}
138
139impl<Attributes, Header, L, R> PayloadAttributesBuilder<Attributes, Header> for Either<L, R>
140where
141 L: PayloadAttributesBuilder<Attributes, Header>,
142 R: PayloadAttributesBuilder<Attributes, Header>,
143{
144 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
145 match self {
146 Self::Left(l) => l.build(parent),
147 Self::Right(r) => r.build(parent),
148 }
149 }
150}
151
152impl<Attributes, Header> PayloadAttributesBuilder<Attributes, Header>
153 for Box<dyn PayloadAttributesBuilder<Attributes, Header>>
154where
155 Header: 'static,
156 Attributes: 'static,
157{
158 fn build(&self, parent: &SealedHeader<Header>) -> Attributes {
159 self.as_ref().build(parent)
160 }
161}
162
163pub trait BuildNextEnv<Attributes, Header, Ctx>: Sized {
167 fn build_next_env(
169 attributes: &Attributes,
170 parent: &SealedHeader<Header>,
171 ctx: &Ctx,
172 ) -> Result<Self, PayloadBuilderError>;
173}
174
175pub fn payload_id(
179 parent: &B256,
180 attributes: &alloy_rpc_types_engine::PayloadAttributes,
181) -> PayloadId {
182 use sha2::Digest;
183 let mut hasher = sha2::Sha256::new();
184 hasher.update(parent.as_slice());
185 hasher.update(&attributes.timestamp.to_be_bytes()[..]);
186 hasher.update(attributes.prev_randao.as_slice());
187 hasher.update(attributes.suggested_fee_recipient.as_slice());
188 if let Some(withdrawals) = &attributes.withdrawals {
189 let mut buf = Vec::new();
190 withdrawals.encode(&mut buf);
191 hasher.update(buf);
192 }
193
194 if let Some(parent_beacon_block) = attributes.parent_beacon_block_root {
195 hasher.update(parent_beacon_block);
196 }
197
198 let out = hasher.finalize();
199
200 #[allow(deprecated)] PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use alloy_eips::eip4895::Withdrawal;
208 use alloy_primitives::{Address, B64};
209 use core::str::FromStr;
210
211 #[test]
212 fn attributes_serde() {
213 let attributes = r#"{"timestamp":"0x1235","prevRandao":"0xf343b00e02dc34ec0124241f74f32191be28fb370bb48060f5fa4df99bda774c","suggestedFeeRecipient":"0x0000000000000000000000000000000000000000","withdrawals":null,"parentBeaconBlockRoot":null}"#;
214 let _attributes: EthPayloadAttributes = serde_json::from_str(attributes).unwrap();
215 }
216
217 #[test]
218 fn test_payload_id_basic() {
219 let parent =
221 B256::from_str("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a")
222 .unwrap();
223 let attributes = EthPayloadAttributes {
224 timestamp: 0x5,
225 prev_randao: B256::from_str(
226 "0x0000000000000000000000000000000000000000000000000000000000000000",
227 )
228 .unwrap(),
229 suggested_fee_recipient: Address::from_str(
230 "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
231 )
232 .unwrap(),
233 withdrawals: None,
234 parent_beacon_block_root: None,
235 slot_number: None,
236 };
237
238 assert_eq!(
240 payload_id(&parent, &attributes),
241 PayloadId(B64::from_str("0xa247243752eb10b4").unwrap())
242 );
243 }
244
245 #[test]
246 fn test_payload_id_with_withdrawals() {
247 let parent =
249 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
250 .unwrap();
251 let attributes = EthPayloadAttributes {
252 timestamp: 1622553200,
253 prev_randao: B256::from_slice(&[1; 32]),
254 suggested_fee_recipient: Address::from_str(
255 "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
256 )
257 .unwrap(),
258 withdrawals: Some(vec![
259 Withdrawal {
260 index: 1,
261 validator_index: 123,
262 address: Address::from([0xAA; 20]),
263 amount: 10,
264 },
265 Withdrawal {
266 index: 2,
267 validator_index: 456,
268 address: Address::from([0xBB; 20]),
269 amount: 20,
270 },
271 ]),
272 parent_beacon_block_root: None,
273 slot_number: None,
274 };
275
276 assert_eq!(
278 payload_id(&parent, &attributes),
279 PayloadId(B64::from_str("0xedddc2f84ba59865").unwrap())
280 );
281 }
282
283 #[test]
284 fn test_payload_id_with_parent_beacon_block_root() {
285 let parent =
287 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
288 .unwrap();
289 let attributes = EthPayloadAttributes {
290 timestamp: 1622553200,
291 prev_randao: B256::from_str(
292 "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
293 )
294 .unwrap(),
295 suggested_fee_recipient: Address::from_str(
296 "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
297 )
298 .unwrap(),
299 withdrawals: None,
300 parent_beacon_block_root: Some(
301 B256::from_str(
302 "0x2222222222222222222222222222222222222222222222222222222222222222",
303 )
304 .unwrap(),
305 ),
306 slot_number: None,
307 };
308
309 assert_eq!(
311 payload_id(&parent, &attributes),
312 PayloadId(B64::from_str("0x0fc49cd532094cce").unwrap())
313 );
314 }
315}