1use bytes::Bytes;
2use ethrex_rlp::error::RLPDecodeError;
3use serde::{Deserialize, Serialize};
4
5use ethrex_common::{
6 Address, Bloom, H256, U256,
7 constants::DEFAULT_OMMERS_HASH,
8 serde_utils,
9 types::{
10 BlobsBundle, Block, BlockBody, BlockHash, BlockHeader, Transaction, Withdrawal,
11 block_access_list::BlockAccessList, compute_transactions_root, compute_withdrawals_root,
12 requests::EncodedRequests,
13 },
14};
15
16#[derive(Clone, Debug, Deserialize, Serialize)]
17#[serde(rename_all = "camelCase")]
18pub struct ExecutionPayload {
19 pub(crate) parent_hash: H256,
20 pub(crate) fee_recipient: Address,
21 pub(crate) state_root: H256,
22 pub(crate) receipts_root: H256,
23 pub(crate) logs_bloom: Bloom,
24 pub(crate) prev_randao: H256,
25 #[serde(with = "serde_utils::u64::hex_str")]
26 pub block_number: u64,
27 #[serde(with = "serde_utils::u64::hex_str")]
28 pub(crate) gas_limit: u64,
29 #[serde(with = "serde_utils::u64::hex_str")]
30 pub(crate) gas_used: u64,
31 #[serde(with = "serde_utils::u64::hex_str")]
32 pub timestamp: u64,
33 #[serde(with = "serde_utils::bytes")]
34 pub(crate) extra_data: Bytes,
35 #[serde(with = "serde_utils::u64::hex_str")]
36 pub(crate) base_fee_per_gas: u64,
37 pub block_hash: H256,
38 pub(crate) transactions: Vec<EncodedTransaction>,
39 #[serde(skip_serializing_if = "Option::is_none", default)]
40 pub withdrawals: Option<Vec<Withdrawal>>,
41 #[serde(
43 skip_serializing_if = "Option::is_none",
44 with = "serde_utils::u64::hex_str_opt",
45 default
46 )]
47 pub blob_gas_used: Option<u64>,
48 #[serde(
49 skip_serializing_if = "Option::is_none",
50 with = "serde_utils::u64::hex_str_opt",
51 default
52 )]
53 pub excess_blob_gas: Option<u64>,
54 #[serde(
56 skip_serializing_if = "Option::is_none",
57 with = "serde_utils::u64::hex_str_opt",
58 default
59 )]
60 pub slot_number: Option<u64>,
61 #[serde(
63 skip_serializing_if = "Option::is_none",
64 with = "serde_utils::block_access_list::rlp_str_opt",
65 default
66 )]
67 pub block_access_list: Option<BlockAccessList>,
68}
69
70#[derive(Clone, Debug)]
71pub struct EncodedTransaction(pub Bytes);
72
73impl<'de> Deserialize<'de> for EncodedTransaction {
74 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
75 where
76 D: serde::Deserializer<'de>,
77 {
78 Ok(EncodedTransaction(serde_utils::bytes::deserialize(
79 deserializer,
80 )?))
81 }
82}
83
84impl Serialize for EncodedTransaction {
85 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
86 where
87 S: serde::Serializer,
88 {
89 serde_utils::bytes::serialize(&self.0, serializer)
90 }
91}
92
93impl EncodedTransaction {
94 fn decode(&self) -> Result<Transaction, RLPDecodeError> {
99 Transaction::decode_canonical(self.0.as_ref())
100 }
101
102 fn encode(tx: &Transaction) -> Self {
103 Self(Bytes::from(tx.encode_canonical_to_vec()))
104 }
105}
106
107impl ExecutionPayload {
108 pub fn into_block(
111 self,
112 parent_beacon_block_root: Option<H256>,
113 requests_hash: Option<H256>,
114 block_access_list_hash: Option<H256>,
115 ) -> Result<Block, RLPDecodeError> {
116 let body = BlockBody {
117 transactions: self
118 .transactions
119 .iter()
120 .map(|encoded_tx| encoded_tx.decode())
121 .collect::<Result<Vec<_>, RLPDecodeError>>()?,
122 ommers: vec![],
123 withdrawals: self.withdrawals,
124 };
125 let header = BlockHeader {
126 parent_hash: self.parent_hash,
127 ommers_hash: *DEFAULT_OMMERS_HASH,
128 coinbase: self.fee_recipient,
129 state_root: self.state_root,
130 transactions_root: compute_transactions_root(
131 &body.transactions,
132 ðrex_crypto::NativeCrypto,
133 ),
134 receipts_root: self.receipts_root,
135 logs_bloom: self.logs_bloom,
136 difficulty: 0.into(),
137 number: self.block_number,
138 gas_limit: self.gas_limit,
139 gas_used: self.gas_used,
140 timestamp: self.timestamp,
141 extra_data: self.extra_data,
142 prev_randao: self.prev_randao,
143 nonce: 0,
144 base_fee_per_gas: Some(self.base_fee_per_gas),
145 withdrawals_root: body
146 .withdrawals
147 .as_ref()
148 .map(|w| compute_withdrawals_root(w, ðrex_crypto::NativeCrypto)),
149 blob_gas_used: self.blob_gas_used,
150 excess_blob_gas: self.excess_blob_gas,
151 parent_beacon_block_root,
152 requests_hash,
154 slot_number: self.slot_number,
155 block_access_list_hash,
156 ..Default::default()
157 };
158
159 Ok(Block::new(header, body))
160 }
161
162 pub fn from_block(block: Block, block_access_list: Option<BlockAccessList>) -> Self {
163 Self {
164 parent_hash: block.header.parent_hash,
165 fee_recipient: block.header.coinbase,
166 state_root: block.header.state_root,
167 receipts_root: block.header.receipts_root,
168 logs_bloom: block.header.logs_bloom,
169 prev_randao: block.header.prev_randao,
170 block_number: block.header.number,
171 gas_limit: block.header.gas_limit,
172 gas_used: block.header.gas_used,
173 timestamp: block.header.timestamp,
174 extra_data: block.header.extra_data.clone(),
175 base_fee_per_gas: block.header.base_fee_per_gas.unwrap_or_default(),
176 block_hash: block.hash(),
177 transactions: block
178 .body
179 .transactions
180 .iter()
181 .map(EncodedTransaction::encode)
182 .collect(),
183 withdrawals: block.body.withdrawals,
184 blob_gas_used: block.header.blob_gas_used,
185 excess_blob_gas: block.header.excess_blob_gas,
186 slot_number: block.header.slot_number,
187 block_access_list,
188 }
189 }
190}
191
192#[derive(Debug, Deserialize, Serialize)]
193#[serde(rename_all = "camelCase")]
194pub struct PayloadStatus {
195 pub status: PayloadValidationStatus,
196 pub latest_valid_hash: Option<H256>,
197 pub validation_error: Option<String>,
198 #[serde(
199 default,
200 skip_serializing_if = "Option::is_none",
201 with = "optional_hex_bytes"
202 )]
203 pub witness: Option<Bytes>,
204}
205
206#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
207#[serde(rename_all = "UPPERCASE")]
208pub enum PayloadValidationStatus {
209 Valid,
210 Invalid,
211 Syncing,
212 Accepted,
213}
214
215impl PayloadStatus {
216 pub fn invalid_with(latest_valid_hash: H256, error: String) -> Self {
219 PayloadStatus {
220 status: PayloadValidationStatus::Invalid,
221 latest_valid_hash: Some(latest_valid_hash),
222 validation_error: Some(error),
223 witness: None,
224 }
225 }
226
227 pub fn invalid_with_err(error: &str) -> Self {
229 PayloadStatus {
230 status: PayloadValidationStatus::Invalid,
231 latest_valid_hash: None,
232 validation_error: Some(error.to_string()),
233 witness: None,
234 }
235 }
236
237 pub fn invalid_with_hash(hash: BlockHash) -> Self {
239 PayloadStatus {
240 status: PayloadValidationStatus::Invalid,
241 latest_valid_hash: Some(hash),
242 validation_error: None,
243 witness: None,
244 }
245 }
246
247 pub fn syncing() -> Self {
249 PayloadStatus {
250 status: PayloadValidationStatus::Syncing,
251 latest_valid_hash: None,
252 validation_error: None,
253 witness: None,
254 }
255 }
256
257 pub fn valid_with_hash(hash: BlockHash) -> Self {
259 PayloadStatus {
260 status: PayloadValidationStatus::Valid,
261 latest_valid_hash: Some(hash),
262 validation_error: None,
263 witness: None,
264 }
265 }
266 pub fn valid() -> Self {
268 PayloadStatus {
269 status: PayloadValidationStatus::Valid,
270 latest_valid_hash: None,
271 validation_error: None,
272 witness: None,
273 }
274 }
275}
276
277mod optional_hex_bytes {
278 use bytes::Bytes;
279 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
280
281 pub fn serialize<S>(value: &Option<Bytes>, serializer: S) -> Result<S::Ok, S::Error>
282 where
283 S: Serializer,
284 {
285 let hex = value
286 .as_ref()
287 .map(|bytes| format!("0x{}", hex::encode(bytes)));
288 Option::<String>::serialize(&hex, serializer)
289 }
290
291 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Bytes>, D::Error>
292 where
293 D: Deserializer<'de>,
294 {
295 let value = Option::<String>::deserialize(deserializer)?;
296 match value {
297 Some(value) if !value.is_empty() => hex::decode(value.trim_start_matches("0x"))
298 .map(Bytes::from)
299 .map(Some)
300 .map_err(|error| D::Error::custom(error.to_string())),
301 _ => Ok(None),
302 }
303 }
304}
305
306#[derive(Clone, Debug, Serialize, Deserialize)]
307#[serde(rename_all = "camelCase")]
308pub struct ExecutionPayloadBody {
309 pub transactions: Vec<EncodedTransaction>,
310 pub withdrawals: Option<Vec<Withdrawal>>,
311}
312
313impl From<BlockBody> for ExecutionPayloadBody {
314 fn from(body: BlockBody) -> Self {
315 Self {
316 transactions: body
317 .transactions
318 .iter()
319 .map(EncodedTransaction::encode)
320 .collect(),
321 withdrawals: body.withdrawals,
322 }
323 }
324}
325
326#[derive(Clone, Debug, Serialize, Deserialize)]
328#[serde(rename_all = "camelCase")]
329pub struct ExecutionPayloadBodyV2 {
330 pub transactions: Vec<EncodedTransaction>,
331 pub withdrawals: Option<Vec<Withdrawal>>,
332 #[serde(
333 skip_serializing_if = "Option::is_none",
334 with = "serde_utils::block_access_list::rlp_str_opt",
335 default
336 )]
337 pub block_access_list: Option<BlockAccessList>,
338}
339
340impl ExecutionPayloadBodyV2 {
341 pub fn from_body_with_bal(body: BlockBody, bal: Option<BlockAccessList>) -> Self {
342 Self {
343 transactions: body
344 .transactions
345 .iter()
346 .map(EncodedTransaction::encode)
347 .collect(),
348 withdrawals: body.withdrawals,
349 block_access_list: bal,
350 }
351 }
352}
353
354#[derive(Clone, Debug, Serialize, Deserialize)]
355#[serde(rename_all = "camelCase")]
356pub struct ExecutionPayloadResponse {
357 pub execution_payload: ExecutionPayload,
358 pub block_value: U256,
360 pub blobs_bundle: Option<BlobsBundle>,
361 pub should_override_builder: Option<bool>, pub execution_requests: Option<Vec<EncodedRequests>>,
363}
364
365#[derive(Clone, Debug, Serialize, Deserialize)]
366#[serde(rename_all = "camelCase")]
367pub struct ExecutionPayloadResponseV2 {
368 pub execution_payload: ExecutionPayload,
369 pub block_value: U256,
371}
372
373#[cfg(test)]
374mod test {
375 use super::*;
376
377 #[test]
378 fn deserialize_payload_into_block() {
379 let json = r#"{"baseFeePerGas":"0x342770c0","blobGasUsed":"0x0","blockHash":"0x4029a2342bb6d54db91457bc8e442be22b3481df8edea24cc721f9d0649f65be","blockNumber":"0x1","excessBlobGas":"0x0","extraData":"0xd883010e06846765746888676f312e32322e34856c696e7578","feeRecipient":"0x8943545177806ed17b9f23f0a21ee5948ecaa776","gasLimit":"0x17dd79d","gasUsed":"0x401640","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x2971eefd1f71f3548728cad87c16cc91b979ef035054828c59a02e49ae300a84","prevRandao":"0x2971eefd1f71f3548728cad87c16cc91b979ef035054828c59a02e49ae300a84","receiptsRoot":"0x0185e8473b81c3a504c4919249a94a94965a2f61c06367ee6ffb88cb7a3ef02b","stateRoot":"0x0eb8fd0af53174e65bb660d0904e5016425a713d8f11c767c26148b526fc05f3","timestamp":"0x66846fb2","transactions":["0xf86d80843baa0c4082f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee538000808360306ba0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4","0xf86d01843baa0c4082f61894687704db07e902e9a8b3754031d168d46e3d586e870aa87bee538000808360306ba0f6c479c3e9135a61d7cca17b7354ddc311cda2d8df265d0378f940bdefd62b54a077786891b0b6bcd438d8c24d00fa6628bc2f1caa554f9dec0a96daa4f40eb0d7","0xf86d02843baa0c4082f6189415e6a5a2e131dd5467fa1ff3acd104f45ee5940b870aa87bee538000808360306ca084469ec8ee41e9104cbe3ad7e7fe4225de86076dd2783749b099a4d155900305a07e64e8848c692f0fc251e78e6f3c388eb303349f3e247481366517c2a5ae2d89","0xf86d03843baa0c4082f6189480c4c7125967139acaa931ee984a9db4100e0f3b870aa87bee538000808360306ba021d2d8a35b8da03d7e0b494f71c9ed1c28a195b94c298407b81d65163a79fbdaa024a9bfcf5bbe75ba35130fa784ab88cd21c12c4e7daf3464de91bc1ed07d1bf6","0xf86d04843baa0c4082f61894d08a63244fcd28b0aec5075052cdce31ba04fead870aa87bee538000808360306ca07ee42fee5e426595056ad406aa65a3c7adb1d3d77279f56ebe2410bcf5118b2ca07b8a0e1d21578e9043a7331f60bafc71d15788d1a2d70d00b3c46e0856ff56d2","0xf86d05843baa0c4082f618940b06ef8be65fcda88f2dbae5813480f997ee8e35870aa87bee538000808360306ba0620669c8d6a781d3131bca874152bf833622af0edcd2247eab1b086875d5242ba01632353388f46946b5ce037130e92128e5837fe35d6c7de2b9e56a0f8cc1f5e6", "0x02f8ef83301824048413f157f8842daf517a830186a094000000000000000000000000000000000000000080b8807a0a600060a0553db8600060c855c77fb29ecd7661d8aefe101a0db652a728af0fded622ff55d019b545d03a7532932a60ad52604260cd5360bf60ce53609460cf53603e60d05360f560d153bc596000609e55600060c6556000601f556000609155535660556057536055605853606e60595360e7605a5360d0605b5360eb60c080a03acb03b1fc20507bc66210f7e18ff5af65038fb22c626ae488ad9513d9b6debca05d38459e9d2a221eb345b0c2761b719b313d062ff1ea3d10cf5b8762c44385a6"],"withdrawals":[]}"#;
381 let payload: ExecutionPayload = serde_json::from_str(json).unwrap();
382 assert!(payload.into_block(Some(H256::zero()), None, None).is_ok());
383 }
384
385 #[test]
386 fn payload_status_omits_absent_witness() {
387 let status = PayloadStatus::valid_with_hash(H256::zero());
388 let json = serde_json::to_value(status).unwrap();
389
390 assert!(json.get("witness").is_none());
391 }
392
393 #[test]
394 fn payload_status_serializes_witness_as_hex() {
395 let mut status = PayloadStatus::valid_with_hash(H256::zero());
396 status.witness = Some(Bytes::from_static(&[0x12, 0x34]));
397
398 let json = serde_json::to_value(status).unwrap();
399
400 assert_eq!(json["witness"], "0x1234");
401 }
402}