reth_ethereum_engine_primitives/
payload.rs1use alloy_eips::{eip4844::BlobTransactionSidecar, eip4895::Withdrawals, eip7685::Requests};
4use alloy_primitives::{Address, B256, U256};
5use alloy_rlp::Encodable;
6use alloy_rpc_types_engine::{
7 ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4,
8 ExecutionPayloadV1, PayloadAttributes, PayloadId,
9};
10use reth_chain_state::ExecutedBlock;
11use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes};
12use reth_primitives::SealedBlock;
13use reth_rpc_types_compat::engine::payload::{
14 block_to_payload_v1, block_to_payload_v3, convert_block_to_payload_field_v2,
15};
16use std::{convert::Infallible, sync::Arc};
17
18#[derive(Debug, Clone)]
24pub struct EthBuiltPayload {
25 pub(crate) id: PayloadId,
27 pub(crate) block: Arc<SealedBlock>,
29 pub(crate) executed_block: Option<ExecutedBlock>,
31 pub(crate) fees: U256,
33 pub(crate) sidecars: Vec<BlobTransactionSidecar>,
36 pub(crate) requests: Option<Requests>,
38}
39
40impl EthBuiltPayload {
43 pub const fn new(
47 id: PayloadId,
48 block: Arc<SealedBlock>,
49 fees: U256,
50 executed_block: Option<ExecutedBlock>,
51 requests: Option<Requests>,
52 ) -> Self {
53 Self { id, block, executed_block, fees, sidecars: Vec::new(), requests }
54 }
55
56 pub const fn id(&self) -> PayloadId {
58 self.id
59 }
60
61 pub fn block(&self) -> &SealedBlock {
63 &self.block
64 }
65
66 pub const fn fees(&self) -> U256 {
68 self.fees
69 }
70
71 pub fn sidecars(&self) -> &[BlobTransactionSidecar] {
73 &self.sidecars
74 }
75
76 pub fn extend_sidecars(&mut self, sidecars: impl IntoIterator<Item = BlobTransactionSidecar>) {
78 self.sidecars.extend(sidecars)
79 }
80
81 pub fn with_sidecars(
83 mut self,
84 sidecars: impl IntoIterator<Item = BlobTransactionSidecar>,
85 ) -> Self {
86 self.extend_sidecars(sidecars);
87 self
88 }
89}
90
91impl BuiltPayload for EthBuiltPayload {
92 fn block(&self) -> &SealedBlock {
93 &self.block
94 }
95
96 fn fees(&self) -> U256 {
97 self.fees
98 }
99
100 fn executed_block(&self) -> Option<ExecutedBlock> {
101 self.executed_block.clone()
102 }
103
104 fn requests(&self) -> Option<Requests> {
105 self.requests.clone()
106 }
107}
108
109impl BuiltPayload for &EthBuiltPayload {
110 fn block(&self) -> &SealedBlock {
111 (**self).block()
112 }
113
114 fn fees(&self) -> U256 {
115 (**self).fees()
116 }
117
118 fn executed_block(&self) -> Option<ExecutedBlock> {
119 self.executed_block.clone()
120 }
121
122 fn requests(&self) -> Option<Requests> {
123 self.requests.clone()
124 }
125}
126
127impl From<EthBuiltPayload> for ExecutionPayloadV1 {
129 fn from(value: EthBuiltPayload) -> Self {
130 block_to_payload_v1(Arc::unwrap_or_clone(value.block))
131 }
132}
133
134impl From<EthBuiltPayload> for ExecutionPayloadEnvelopeV2 {
136 fn from(value: EthBuiltPayload) -> Self {
137 let EthBuiltPayload { block, fees, .. } = value;
138
139 Self {
140 block_value: fees,
141 execution_payload: convert_block_to_payload_field_v2(Arc::unwrap_or_clone(block)),
142 }
143 }
144}
145
146impl From<EthBuiltPayload> for ExecutionPayloadEnvelopeV3 {
147 fn from(value: EthBuiltPayload) -> Self {
148 let EthBuiltPayload { block, fees, sidecars, .. } = value;
149
150 Self {
151 execution_payload: block_to_payload_v3(Arc::unwrap_or_clone(block)),
152 block_value: fees,
153 should_override_builder: false,
162 blobs_bundle: sidecars.into(),
163 }
164 }
165}
166
167impl From<EthBuiltPayload> for ExecutionPayloadEnvelopeV4 {
168 fn from(value: EthBuiltPayload) -> Self {
169 let EthBuiltPayload { block, fees, sidecars, requests, .. } = value;
170
171 Self {
172 execution_payload: block_to_payload_v3(Arc::unwrap_or_clone(block)),
173 block_value: fees,
174 should_override_builder: false,
183 blobs_bundle: sidecars.into_iter().map(Into::into).collect::<Vec<_>>().into(),
184 execution_requests: requests.unwrap_or_default().take(),
185 }
186 }
187}
188
189#[derive(Debug, Clone, PartialEq, Eq, Default)]
191pub struct EthPayloadBuilderAttributes {
192 pub id: PayloadId,
194 pub parent: B256,
196 pub timestamp: u64,
200 pub suggested_fee_recipient: Address,
202 pub prev_randao: B256,
204 pub withdrawals: Withdrawals,
206 pub parent_beacon_block_root: Option<B256>,
208}
209
210impl EthPayloadBuilderAttributes {
213 pub const fn payload_id(&self) -> PayloadId {
215 self.id
216 }
217
218 pub fn new(parent: B256, attributes: PayloadAttributes) -> Self {
222 let id = payload_id(&parent, &attributes);
223
224 Self {
225 id,
226 parent,
227 timestamp: attributes.timestamp,
228 suggested_fee_recipient: attributes.suggested_fee_recipient,
229 prev_randao: attributes.prev_randao,
230 withdrawals: attributes.withdrawals.unwrap_or_default().into(),
231 parent_beacon_block_root: attributes.parent_beacon_block_root,
232 }
233 }
234}
235
236impl PayloadBuilderAttributes for EthPayloadBuilderAttributes {
237 type RpcPayloadAttributes = PayloadAttributes;
238 type Error = Infallible;
239
240 fn try_new(
244 parent: B256,
245 attributes: PayloadAttributes,
246 _version: u8,
247 ) -> Result<Self, Infallible> {
248 Ok(Self::new(parent, attributes))
249 }
250
251 fn payload_id(&self) -> PayloadId {
252 self.id
253 }
254
255 fn parent(&self) -> B256 {
256 self.parent
257 }
258
259 fn timestamp(&self) -> u64 {
260 self.timestamp
261 }
262
263 fn parent_beacon_block_root(&self) -> Option<B256> {
264 self.parent_beacon_block_root
265 }
266
267 fn suggested_fee_recipient(&self) -> Address {
268 self.suggested_fee_recipient
269 }
270
271 fn prev_randao(&self) -> B256 {
272 self.prev_randao
273 }
274
275 fn withdrawals(&self) -> &Withdrawals {
276 &self.withdrawals
277 }
278}
279
280pub(crate) fn payload_id(parent: &B256, attributes: &PayloadAttributes) -> PayloadId {
284 use sha2::Digest;
285 let mut hasher = sha2::Sha256::new();
286 hasher.update(parent.as_slice());
287 hasher.update(&attributes.timestamp.to_be_bytes()[..]);
288 hasher.update(attributes.prev_randao.as_slice());
289 hasher.update(attributes.suggested_fee_recipient.as_slice());
290 if let Some(withdrawals) = &attributes.withdrawals {
291 let mut buf = Vec::new();
292 withdrawals.encode(&mut buf);
293 hasher.update(buf);
294 }
295
296 if let Some(parent_beacon_block) = attributes.parent_beacon_block_root {
297 hasher.update(parent_beacon_block);
298 }
299
300 let out = hasher.finalize();
301 PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use alloy_eips::eip4895::Withdrawal;
308 use alloy_primitives::B64;
309 use std::str::FromStr;
310
311 #[test]
312 fn attributes_serde() {
313 let attributes = r#"{"timestamp":"0x1235","prevRandao":"0xf343b00e02dc34ec0124241f74f32191be28fb370bb48060f5fa4df99bda774c","suggestedFeeRecipient":"0x0000000000000000000000000000000000000000","withdrawals":null,"parentBeaconBlockRoot":null}"#;
314 let _attributes: PayloadAttributes = serde_json::from_str(attributes).unwrap();
315 }
316
317 #[test]
318 fn test_payload_id_basic() {
319 let parent =
321 B256::from_str("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a")
322 .unwrap();
323 let attributes = PayloadAttributes {
324 timestamp: 0x5,
325 prev_randao: B256::from_str(
326 "0x0000000000000000000000000000000000000000000000000000000000000000",
327 )
328 .unwrap(),
329 suggested_fee_recipient: Address::from_str(
330 "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
331 )
332 .unwrap(),
333 withdrawals: None,
334 parent_beacon_block_root: None,
335 target_blobs_per_block: None,
336 max_blobs_per_block: None,
337 };
338
339 assert_eq!(
341 payload_id(&parent, &attributes),
342 PayloadId(B64::from_str("0xa247243752eb10b4").unwrap())
343 );
344 }
345
346 #[test]
347 fn test_payload_id_with_withdrawals() {
348 let parent =
350 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
351 .unwrap();
352 let attributes = PayloadAttributes {
353 timestamp: 1622553200,
354 prev_randao: B256::from_slice(&[1; 32]),
355 suggested_fee_recipient: Address::from_str(
356 "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
357 )
358 .unwrap(),
359 withdrawals: Some(vec![
360 Withdrawal {
361 index: 1,
362 validator_index: 123,
363 address: Address::from([0xAA; 20]),
364 amount: 10,
365 },
366 Withdrawal {
367 index: 2,
368 validator_index: 456,
369 address: Address::from([0xBB; 20]),
370 amount: 20,
371 },
372 ]),
373 parent_beacon_block_root: None,
374 target_blobs_per_block: None,
375 max_blobs_per_block: None,
376 };
377
378 assert_eq!(
380 payload_id(&parent, &attributes),
381 PayloadId(B64::from_str("0xedddc2f84ba59865").unwrap())
382 );
383 }
384
385 #[test]
386 fn test_payload_id_with_parent_beacon_block_root() {
387 let parent =
389 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
390 .unwrap();
391 let attributes = PayloadAttributes {
392 timestamp: 1622553200,
393 prev_randao: B256::from_str(
394 "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
395 )
396 .unwrap(),
397 suggested_fee_recipient: Address::from_str(
398 "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
399 )
400 .unwrap(),
401 withdrawals: None,
402 parent_beacon_block_root: Some(
403 B256::from_str(
404 "0x2222222222222222222222222222222222222222222222222222222222222222",
405 )
406 .unwrap(),
407 ),
408 target_blobs_per_block: None,
409 max_blobs_per_block: None,
410 };
411
412 assert_eq!(
414 payload_id(&parent, &attributes),
415 PayloadId(B64::from_str("0x0fc49cd532094cce").unwrap())
416 );
417 }
418}