Skip to main content

ethrex_rpc/engine/
payload.rs

1use bytes::Bytes;
2use ethrex_blockchain::error::ChainError;
3use ethrex_blockchain::payload::PayloadBuildResult;
4use ethrex_common::types::block_access_list::BlockAccessList;
5use ethrex_common::types::block_execution_witness::{
6    ExecutionWitness, ExtWitness, RpcExecutionWitness,
7};
8use ethrex_common::types::payload::PayloadBundle;
9use ethrex_common::types::requests::{EncodedRequests, compute_requests_hash};
10use ethrex_common::types::{Block, BlockBody, BlockHash, BlockHeader, BlockNumber, Fork};
11use ethrex_common::{H256, U256};
12use ethrex_crypto::NativeCrypto;
13use ethrex_p2p::sync::SyncMode;
14use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode, error::RLPDecodeError};
15use serde_json::Value;
16use tokio::sync::oneshot;
17use tracing::{debug, error, info, warn};
18
19use crate::rpc::{RpcApiContext, RpcHandler};
20use crate::types::payload::{
21    ExecutionPayload, ExecutionPayloadBody, ExecutionPayloadBodyV2, ExecutionPayloadResponse,
22    PayloadStatus,
23};
24use crate::utils::RpcErr;
25use crate::utils::{RpcRequest, parse_json_hex};
26
27// The Engine API (Shanghai) only mandates supporting request sizes of at least 32 blocks.
28// Cap at MAX_REQUEST_BLOCKS = 1024, the largest request a conforming consensus client makes.
29// -> https://github.com/ethereum/consensus-specs/blob/a84880a47a88700d8dfa451c2a7cd4b3f309bd0d/specs/phase0/p2p-interface.md#configuration
30const GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE: u64 = 1024;
31
32// NewPayload V1-V2-V3 implementations
33pub struct NewPayloadV1Request {
34    pub payload: ExecutionPayload,
35}
36
37impl RpcHandler for NewPayloadV1Request {
38    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
39        Ok(NewPayloadV1Request {
40            payload: parse_execution_payload(params)?,
41        })
42    }
43
44    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
45        validate_execution_payload_v1(&self.payload)?;
46        let block = match get_block_from_payload(&self.payload, None, None, None) {
47            Ok(block) => block,
48            Err(err) => {
49                return Ok(serde_json::to_value(PayloadStatus::invalid_with_err(
50                    &err.to_string(),
51                ))?);
52            }
53        };
54        let payload_status =
55            handle_new_payload_v1_v2(&self.payload, block, context, None, false).await?;
56        serde_json::to_value(payload_status).map_err(|error| RpcErr::Internal(error.to_string()))
57    }
58}
59
60pub struct NewPayloadV2Request {
61    pub payload: ExecutionPayload,
62}
63
64impl RpcHandler for NewPayloadV2Request {
65    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
66        Ok(NewPayloadV2Request {
67            payload: parse_execution_payload(params)?,
68        })
69    }
70
71    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
72        let chain_config = &context.storage.get_chain_config();
73        if chain_config.is_shanghai_activated(self.payload.timestamp) {
74            validate_execution_payload_v2(&self.payload)?;
75        } else {
76            // Behave as a v1
77            validate_execution_payload_v1(&self.payload)?;
78        }
79        let block = match get_block_from_payload(&self.payload, None, None, None) {
80            Ok(block) => block,
81            Err(err) => {
82                return Ok(serde_json::to_value(PayloadStatus::invalid_with_err(
83                    &err.to_string(),
84                ))?);
85            }
86        };
87        let payload_status =
88            handle_new_payload_v1_v2(&self.payload, block, context, None, false).await?;
89        serde_json::to_value(payload_status).map_err(|error| RpcErr::Internal(error.to_string()))
90    }
91}
92
93pub struct NewPayloadV3Request {
94    pub payload: ExecutionPayload,
95    pub expected_blob_versioned_hashes: Vec<H256>,
96    pub parent_beacon_block_root: H256,
97}
98
99impl From<NewPayloadV3Request> for RpcRequest {
100    fn from(val: NewPayloadV3Request) -> Self {
101        RpcRequest {
102            method: "engine_newPayloadV3".to_string(),
103            params: Some(vec![
104                serde_json::json!(val.payload),
105                serde_json::json!(val.expected_blob_versioned_hashes),
106                serde_json::json!(val.parent_beacon_block_root),
107            ]),
108            ..Default::default()
109        }
110    }
111}
112
113impl RpcHandler for NewPayloadV3Request {
114    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
115        let params = params
116            .as_ref()
117            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
118        if params.len() != 3 {
119            return Err(RpcErr::BadParams("Expected 3 params".to_owned()));
120        }
121        Ok(NewPayloadV3Request {
122            payload: serde_json::from_value(params[0].clone())
123                .map_err(|_| RpcErr::WrongParam("payload".to_string()))?,
124            expected_blob_versioned_hashes: serde_json::from_value(params[1].clone())
125                .map_err(|_| RpcErr::WrongParam("expected_blob_versioned_hashes".to_string()))?,
126            parent_beacon_block_root: serde_json::from_value(params[2].clone())
127                .map_err(|_| RpcErr::WrongParam("parent_beacon_block_root".to_string()))?,
128        })
129    }
130
131    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
132        let block = match get_block_from_payload(
133            &self.payload,
134            Some(self.parent_beacon_block_root),
135            None,
136            None,
137        ) {
138            Ok(block) => block,
139            Err(err) => {
140                return Ok(serde_json::to_value(PayloadStatus::invalid_with_err(
141                    &err.to_string(),
142                ))?);
143            }
144        };
145        validate_fork(&block, Fork::Cancun, &context)?;
146        validate_execution_payload_v3(&self.payload)?;
147        let payload_status = handle_new_payload_v3(
148            &self.payload,
149            context,
150            block,
151            self.expected_blob_versioned_hashes.clone(),
152            None,
153            false,
154        )
155        .await?;
156        serde_json::to_value(payload_status).map_err(|error| RpcErr::Internal(error.to_string()))
157    }
158}
159
160pub struct NewPayloadV4Request {
161    pub payload: ExecutionPayload,
162    pub expected_blob_versioned_hashes: Vec<H256>,
163    pub parent_beacon_block_root: H256,
164    pub execution_requests: Vec<EncodedRequests>,
165}
166
167impl From<NewPayloadV4Request> for RpcRequest {
168    fn from(val: NewPayloadV4Request) -> Self {
169        RpcRequest {
170            method: "engine_newPayloadV4".to_string(),
171            params: Some(vec![
172                serde_json::json!(val.payload),
173                serde_json::json!(val.expected_blob_versioned_hashes),
174                serde_json::json!(val.parent_beacon_block_root),
175                serde_json::json!(val.execution_requests),
176            ]),
177            ..Default::default()
178        }
179    }
180}
181
182impl RpcHandler for NewPayloadV4Request {
183    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
184        let params = params
185            .as_ref()
186            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
187        if params.len() != 4 {
188            return Err(RpcErr::BadParams("Expected 4 params".to_owned()));
189        }
190        Ok(NewPayloadV4Request {
191            payload: serde_json::from_value(params[0].clone())
192                .map_err(|_| RpcErr::WrongParam("payload".to_string()))?,
193            expected_blob_versioned_hashes: serde_json::from_value(params[1].clone())
194                .map_err(|_| RpcErr::WrongParam("expected_blob_versioned_hashes".to_string()))?,
195            parent_beacon_block_root: serde_json::from_value(params[2].clone())
196                .map_err(|_| RpcErr::WrongParam("parent_beacon_block_root".to_string()))?,
197            execution_requests: serde_json::from_value(params[3].clone())
198                .map_err(|_| RpcErr::WrongParam("execution_requests".to_string()))?,
199        })
200    }
201
202    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
203        // EIP-7928 / Amsterdam: V4 payloads MUST NOT include the BAL field — that
204        // field belongs to V5. Per engine-API spec, structurally-invalid payloads
205        // return JSON-RPC -32602 (Invalid params), not PayloadStatus.INVALID.
206        if self.payload.block_access_list.is_some() {
207            return Err(RpcErr::WrongParam(
208                "block_access_list not allowed in engine_newPayloadV4".to_string(),
209            ));
210        }
211
212        // validate the received requests
213        validate_execution_requests(&self.execution_requests)?;
214
215        let requests_hash = compute_requests_hash(&self.execution_requests);
216        let block = match get_block_from_payload(
217            &self.payload,
218            Some(self.parent_beacon_block_root),
219            Some(requests_hash),
220            None,
221        ) {
222            Ok(block) => block,
223            Err(err) => {
224                return Ok(serde_json::to_value(PayloadStatus::invalid_with_err(
225                    &err.to_string(),
226                ))?);
227            }
228        };
229
230        let chain_config = context.storage.get_chain_config();
231
232        // Amsterdam-active timestamps must use V5, not V4. Per engine-API spec
233        // (amsterdam.md): "Client software MUST return -38005: Unsupported fork
234        // if the timestamp of payload is greater than or equal to the Amsterdam
235        // activation timestamp."
236        if chain_config.is_amsterdam_activated(block.header.timestamp) {
237            return Err(RpcErr::UnsupportedFork(format!(
238                "{:?}",
239                chain_config.get_fork(block.header.timestamp)
240            )));
241        }
242
243        if !chain_config.is_prague_activated(block.header.timestamp) {
244            return Err(RpcErr::UnsupportedFork(format!(
245                "{:?}",
246                chain_config.get_fork(block.header.timestamp)
247            )));
248        }
249
250        // A pre-Amsterdam header that carries block_access_list_hash produces a
251        // block_hash that won't match the one ethrex reconstructs (the field is
252        // omitted from the V4 header schema). That mismatch is surfaced as
253        // PayloadStatus.INVALID via the normal block-hash check, matching the EELS
254        // fixture test_invalid_pre_fork_block_with_bal_hash_field
255        // [fork_BPO2ToAmsterdamAtTime15k-blockchain_test_engine] (INVALID_BLOCK_HASH,
256        // no engine API error code).
257
258        // We use v3 since the execution payload remains the same.
259        validate_execution_payload_v3(&self.payload)?;
260        let payload_status = handle_new_payload_v3(
261            &self.payload,
262            context,
263            block,
264            self.expected_blob_versioned_hashes.clone(),
265            None,
266            false,
267        )
268        .await?;
269        serde_json::to_value(payload_status).map_err(|error| RpcErr::Internal(error.to_string()))
270    }
271}
272
273pub struct NewPayloadV5Request {
274    pub payload: ExecutionPayload,
275    pub expected_blob_versioned_hashes: Vec<H256>,
276    pub parent_beacon_block_root: H256,
277    pub execution_requests: Vec<EncodedRequests>,
278    /// The BAL hash computed from the raw RLP bytes as received (no re-encoding/sorting).
279    /// This preserves the exact encoding from the payload for block hash validation.
280    pub raw_bal_hash: Option<H256>,
281}
282
283impl From<NewPayloadV5Request> for RpcRequest {
284    fn from(val: NewPayloadV5Request) -> Self {
285        RpcRequest {
286            method: "engine_newPayloadV5".to_string(),
287            params: Some(vec![
288                serde_json::json!(val.payload),
289                serde_json::json!(val.expected_blob_versioned_hashes),
290                serde_json::json!(val.parent_beacon_block_root),
291                serde_json::json!(val.execution_requests),
292            ]),
293            ..Default::default()
294        }
295    }
296}
297
298impl RpcHandler for NewPayloadV5Request {
299    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
300        let params = params
301            .as_ref()
302            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
303        if params.len() != 4 {
304            return Err(RpcErr::BadParams("Expected 4 params".to_owned()));
305        }
306
307        // Extract the raw BAL hash from the JSON payload before deserialization.
308        // We hash the raw RLP bytes as-received to preserve the exact encoding
309        // (including any ordering) for accurate block hash validation.
310        let raw_bal_hash = params[0]
311            .get("blockAccessList")
312            .map(|v| {
313                let hex_str = v
314                    .as_str()
315                    .ok_or(RpcErr::WrongParam("blockAccessList".to_string()))?;
316                let bytes = hex::decode(hex_str.trim_start_matches("0x"))
317                    .map_err(|_| RpcErr::WrongParam("blockAccessList".to_string()))?;
318                Ok::<_, RpcErr>(ethrex_common::utils::keccak(bytes))
319            })
320            .transpose()?;
321
322        Ok(Self {
323            payload: serde_json::from_value(params[0].clone())
324                .map_err(|_| RpcErr::WrongParam("payload".to_string()))?,
325            expected_blob_versioned_hashes: serde_json::from_value(params[1].clone())
326                .map_err(|_| RpcErr::WrongParam("expected_blob_versioned_hashes".to_string()))?,
327            parent_beacon_block_root: serde_json::from_value(params[2].clone())
328                .map_err(|_| RpcErr::WrongParam("parent_beacon_block_root".to_string()))?,
329            execution_requests: serde_json::from_value(params[3].clone())
330                .map_err(|_| RpcErr::WrongParam("execution_requests".to_string()))?,
331            raw_bal_hash,
332        })
333    }
334
335    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
336        self.handle_with_witness(context, false).await
337    }
338}
339
340impl NewPayloadV5Request {
341    async fn handle_with_witness(
342        &self,
343        context: RpcApiContext,
344        make_witness: bool,
345    ) -> Result<Value, RpcErr> {
346        validate_execution_payload_v5(&self.payload)?;
347
348        // validate the received requests
349        validate_execution_requests(&self.execution_requests)?;
350
351        let requests_hash = compute_requests_hash(&self.execution_requests);
352        // Use the hash computed from the raw RLP bytes as-received.
353        // This preserves the exact encoding (including any ordering) from the payload,
354        // so the block hash check correctly detects BAL corruption.
355        let block_access_list_hash = self.raw_bal_hash;
356
357        let block = match get_block_from_payload(
358            &self.payload,
359            Some(self.parent_beacon_block_root),
360            Some(requests_hash),
361            block_access_list_hash,
362        ) {
363            Ok(block) => block,
364            Err(err) => {
365                return Ok(serde_json::to_value(PayloadStatus::invalid_with_err(
366                    &err.to_string(),
367                ))?);
368            }
369        };
370
371        let chain_config = context.storage.get_chain_config();
372
373        // Pre-Amsterdam timestamps must use V4, not V5. Per engine-API spec
374        // (amsterdam.md): "Client software MUST return -38005: Unsupported fork
375        // if the timestamp of the payload does not fall within the time frame of
376        // the Amsterdam activation." Symmetric with the V4+Amsterdam case above.
377        if !chain_config.is_amsterdam_activated(block.header.timestamp) {
378            return Err(RpcErr::UnsupportedFork(format!(
379                "{:?}",
380                chain_config.get_fork(block.header.timestamp)
381            )));
382        }
383
384        // EIP-7928 fork-boundary detector: V5 requires block_access_list_hash in
385        // the header. If the payload's block_hash matches what a V4-style header
386        // (without the field) would produce, the sender used the wrong API
387        // version; reject with -32602 (InvalidParams) to match the EELS fixture
388        // test_invalid_post_fork_block_without_bal_hash_field
389        // [fork_BPO2ToAmsterdamAtTime15k-blockchain_test_engine]. Real
390        // value-mismatch tests don't match this alternate and fall through to
391        // PayloadStatus.INVALID.
392        if block.hash() != self.payload.block_hash {
393            let mut alt_header = block.header.clone();
394            alt_header.block_access_list_hash = None;
395            let alt_hash = alt_header.compute_block_hash(&ethrex_crypto::NativeCrypto);
396            if alt_hash == self.payload.block_hash {
397                return Err(RpcErr::WrongParam(
398                    "engine_newPayloadV5 received header missing block_access_list_hash field"
399                        .to_string(),
400                ));
401            }
402        }
403
404        let bal = self.payload.block_access_list.clone();
405        let payload_status = handle_new_payload_v4(
406            &self.payload,
407            context,
408            block,
409            self.expected_blob_versioned_hashes.clone(),
410            bal,
411            make_witness,
412        )
413        .await?;
414        serde_json::to_value(payload_status).map_err(|error| RpcErr::Internal(error.to_string()))
415    }
416}
417
418pub struct NewPayloadWithWitnessV5Request(pub NewPayloadV5Request);
419
420impl From<NewPayloadWithWitnessV5Request> for RpcRequest {
421    fn from(val: NewPayloadWithWitnessV5Request) -> Self {
422        RpcRequest {
423            method: "engine_newPayloadWithWitnessV5".to_string(),
424            params: Some(vec![
425                serde_json::json!(val.0.payload),
426                serde_json::json!(val.0.expected_blob_versioned_hashes),
427                serde_json::json!(val.0.parent_beacon_block_root),
428                serde_json::json!(val.0.execution_requests),
429            ]),
430            ..Default::default()
431        }
432    }
433}
434
435impl RpcHandler for NewPayloadWithWitnessV5Request {
436    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
437        NewPayloadV5Request::parse(params).map(Self)
438    }
439
440    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
441        self.0.handle_with_witness(context, true).await
442    }
443}
444
445// GetPayload V1-V2-V3 implementations
446pub struct GetPayloadV1Request {
447    pub payload_id: u64,
448}
449
450impl RpcHandler for GetPayloadV1Request {
451    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
452        let payload_id = parse_get_payload_request(params)?;
453        Ok(Self { payload_id })
454    }
455
456    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
457        let payload_bundle = get_payload(self.payload_id, &context).await?;
458        // NOTE: This validation is actually not required to run Hive tests. Not sure if it's
459        // necessary
460        validate_payload_v1_v2(&payload_bundle.block, &context)?;
461
462        // V1 doesn't support BAL (pre-EIP-7928)
463        let response = ExecutionPayload::from_block(payload_bundle.block, None);
464
465        serde_json::to_value(response).map_err(|error| RpcErr::Internal(error.to_string()))
466    }
467}
468
469pub struct GetPayloadV2Request {
470    pub payload_id: u64,
471}
472
473impl RpcHandler for GetPayloadV2Request {
474    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
475        let payload_id = parse_get_payload_request(params)?;
476        Ok(Self { payload_id })
477    }
478
479    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
480        let payload_bundle = get_payload(self.payload_id, &context).await?;
481        validate_payload_v1_v2(&payload_bundle.block, &context)?;
482
483        // V2 doesn't support BAL (pre-EIP-7928)
484        let response = ExecutionPayloadResponse {
485            execution_payload: ExecutionPayload::from_block(payload_bundle.block, None),
486            block_value: payload_bundle.block_value,
487            blobs_bundle: None,
488            should_override_builder: None,
489            execution_requests: None,
490        };
491
492        serde_json::to_value(response).map_err(|error| RpcErr::Internal(error.to_string()))
493    }
494}
495
496pub struct GetPayloadV3Request {
497    pub payload_id: u64,
498}
499
500impl From<GetPayloadV3Request> for RpcRequest {
501    fn from(val: GetPayloadV3Request) -> Self {
502        RpcRequest {
503            method: "engine_getPayloadV3".to_string(),
504            params: Some(vec![serde_json::json!(U256::from(val.payload_id))]),
505            ..Default::default()
506        }
507    }
508}
509
510impl RpcHandler for GetPayloadV3Request {
511    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
512        let payload_id = parse_get_payload_request(params)?;
513        Ok(Self { payload_id })
514    }
515
516    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
517        let payload_bundle = get_payload(self.payload_id, &context).await?;
518        validate_fork(&payload_bundle.block, Fork::Cancun, &context)?;
519
520        // V3 doesn't support BAL (Cancun fork, pre-EIP-7928)
521        let response = ExecutionPayloadResponse {
522            execution_payload: ExecutionPayload::from_block(payload_bundle.block, None),
523            block_value: payload_bundle.block_value,
524            blobs_bundle: Some(payload_bundle.blobs_bundle),
525            should_override_builder: Some(false),
526            execution_requests: None,
527        };
528
529        serde_json::to_value(response).map_err(|error| RpcErr::Internal(error.to_string()))
530    }
531}
532
533pub struct GetPayloadV4Request {
534    pub payload_id: u64,
535}
536
537impl From<GetPayloadV4Request> for RpcRequest {
538    fn from(val: GetPayloadV4Request) -> Self {
539        RpcRequest {
540            method: "engine_getPayloadV4".to_string(),
541            params: Some(vec![serde_json::json!(U256::from(val.payload_id))]),
542            ..Default::default()
543        }
544    }
545}
546
547impl RpcHandler for GetPayloadV4Request {
548    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
549        let payload_id = parse_get_payload_request(params)?;
550        Ok(Self { payload_id })
551    }
552
553    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
554        let payload_bundle = get_payload(self.payload_id, &context).await?;
555        let chain_config = &context.storage.get_chain_config();
556
557        if !chain_config.is_prague_activated(payload_bundle.block.header.timestamp) {
558            return Err(RpcErr::UnsupportedFork(format!(
559                "{:?}",
560                chain_config.get_fork(payload_bundle.block.header.timestamp)
561            )));
562        }
563        if chain_config.is_osaka_activated(payload_bundle.block.header.timestamp) {
564            return Err(RpcErr::UnsupportedFork(format!("{:?}", Fork::Osaka)));
565        }
566
567        // V4 doesn't support BAL (Prague fork, pre-EIP-7928)
568        let response = ExecutionPayloadResponse {
569            execution_payload: ExecutionPayload::from_block(payload_bundle.block, None),
570            block_value: payload_bundle.block_value,
571            blobs_bundle: Some(payload_bundle.blobs_bundle),
572            should_override_builder: Some(false),
573            execution_requests: Some(
574                payload_bundle
575                    .requests
576                    .into_iter()
577                    .filter(|r| !r.is_empty())
578                    .collect(),
579            ),
580        };
581
582        serde_json::to_value(response).map_err(|error| RpcErr::Internal(error.to_string()))
583    }
584}
585
586pub struct GetPayloadV5Request {
587    pub payload_id: u64,
588}
589
590impl From<GetPayloadV5Request> for RpcRequest {
591    fn from(val: GetPayloadV5Request) -> Self {
592        RpcRequest {
593            method: "engine_getPayloadV5".to_string(),
594            params: Some(vec![serde_json::json!(U256::from(val.payload_id))]),
595            ..Default::default()
596        }
597    }
598}
599
600impl RpcHandler for GetPayloadV5Request {
601    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
602        let payload_id = parse_get_payload_request(params)?;
603        Ok(Self { payload_id })
604    }
605
606    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
607        let payload_bundle = get_payload(self.payload_id, &context).await?;
608        let chain_config = &context.storage.get_chain_config();
609
610        if !chain_config.is_osaka_activated(payload_bundle.block.header.timestamp)
611            || chain_config.is_amsterdam_activated(payload_bundle.block.header.timestamp)
612        {
613            return Err(RpcErr::UnsupportedFork(format!(
614                "{:?}",
615                chain_config.get_fork(payload_bundle.block.header.timestamp)
616            )));
617        }
618
619        // V5 supports BAL before Amsterdam (EIP-7928)
620        let response = ExecutionPayloadResponse {
621            execution_payload: ExecutionPayload::from_block(
622                payload_bundle.block,
623                payload_bundle.block_access_list,
624            ),
625            block_value: payload_bundle.block_value,
626            blobs_bundle: Some(payload_bundle.blobs_bundle),
627            should_override_builder: Some(false),
628            execution_requests: Some(
629                payload_bundle
630                    .requests
631                    .into_iter()
632                    .filter(|r| !r.is_empty())
633                    .collect(),
634            ),
635        };
636
637        serde_json::to_value(response).map_err(|error| RpcErr::Internal(error.to_string()))
638    }
639}
640
641pub struct GetPayloadV6Request {
642    pub payload_id: u64,
643}
644
645impl From<GetPayloadV6Request> for RpcRequest {
646    fn from(val: GetPayloadV6Request) -> Self {
647        RpcRequest {
648            method: "engine_getPayloadV6".to_string(),
649            params: Some(vec![serde_json::json!(U256::from(val.payload_id))]),
650            ..Default::default()
651        }
652    }
653}
654
655impl RpcHandler for GetPayloadV6Request {
656    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
657        let payload_id = parse_get_payload_request(params)?;
658        Ok(Self { payload_id })
659    }
660
661    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
662        let payload_bundle = get_payload(self.payload_id, &context).await?;
663        let chain_config = &context.storage.get_chain_config();
664
665        if !chain_config.is_amsterdam_activated(payload_bundle.block.header.timestamp) {
666            return Err(RpcErr::UnsupportedFork(format!(
667                "{:?}",
668                chain_config.get_fork(payload_bundle.block.header.timestamp)
669            )));
670        }
671
672        // V6 supports BAL (Amsterdam EL fork / Glamsterdam, EIP-7928)
673        let response = ExecutionPayloadResponse {
674            execution_payload: ExecutionPayload::from_block(
675                payload_bundle.block,
676                payload_bundle.block_access_list,
677            ),
678            block_value: payload_bundle.block_value,
679            blobs_bundle: Some(payload_bundle.blobs_bundle),
680            should_override_builder: Some(false),
681            execution_requests: Some(
682                payload_bundle
683                    .requests
684                    .into_iter()
685                    .filter(|r| !r.is_empty())
686                    .collect(),
687            ),
688        };
689
690        serde_json::to_value(response).map_err(|error| RpcErr::Internal(error.to_string()))
691    }
692}
693
694pub struct GetPayloadBodiesByHashV1Request {
695    pub hashes: Vec<BlockHash>,
696}
697
698impl RpcHandler for GetPayloadBodiesByHashV1Request {
699    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
700        let params = params
701            .as_ref()
702            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
703        if params.len() != 1 {
704            return Err(RpcErr::BadParams("Expected 1 param".to_owned()));
705        };
706
707        Ok(GetPayloadBodiesByHashV1Request {
708            hashes: serde_json::from_value(params[0].clone())?,
709        })
710    }
711
712    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
713        if self.hashes.len() as u64 > GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE {
714            return Err(RpcErr::TooLargeRequest);
715        }
716        let mut bodies = Vec::new();
717        for hash in self.hashes.iter() {
718            bodies.push(context.storage.get_block_body_by_hash(*hash).await?)
719        }
720        build_payload_body_response(bodies)
721    }
722}
723
724pub struct GetPayloadBodiesByRangeV1Request {
725    start: BlockNumber,
726    count: u64,
727}
728
729impl RpcHandler for GetPayloadBodiesByRangeV1Request {
730    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
731        let params = params
732            .as_ref()
733            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
734        if params.len() != 2 {
735            return Err(RpcErr::BadParams("Expected 1 param".to_owned()));
736        };
737        let start = parse_json_hex(&params[0]).map_err(|_| RpcErr::BadHexFormat(0))?;
738        let count = parse_json_hex(&params[1]).map_err(|_| RpcErr::BadHexFormat(1))?;
739        if start < 1 {
740            return Err(RpcErr::WrongParam("start".to_owned()));
741        }
742        if count < 1 {
743            return Err(RpcErr::WrongParam("count".to_owned()));
744        }
745        Ok(GetPayloadBodiesByRangeV1Request { start, count })
746    }
747
748    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
749        if self.count > GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE {
750            return Err(RpcErr::TooLargeRequest);
751        }
752        let latest_block_number = context.storage.get_latest_block_number().await?;
753        // NOTE: we truncate the range because the spec says we "MUST NOT return trailing
754        // null values if the request extends past the current latest known block"
755        let last = latest_block_number.min(self.start + self.count - 1);
756        let bodies = context.storage.get_block_bodies(self.start, last).await?;
757        build_payload_body_response(bodies)
758    }
759}
760
761fn build_payload_body_response(bodies: Vec<Option<BlockBody>>) -> Result<Value, RpcErr> {
762    let response: Vec<Option<ExecutionPayloadBody>> = bodies
763        .into_iter()
764        .map(|body| body.map(Into::into))
765        .collect();
766    serde_json::to_value(response).map_err(|error| RpcErr::Internal(error.to_string()))
767}
768
769/// Returns the block's BAL for V2 payload-body responses.
770///
771/// Reads the persisted BAL first; only when it is absent (pre-Amsterdam blocks,
772/// or Amsterdam blocks processed before BAL persistence was added) does it fall
773/// back to regenerating via re-execution, which requires the parent state trie
774/// and fails on snap-synced nodes that don't hold that historical state.
775fn bal_for_block(
776    context: &RpcApiContext,
777    block: &Block,
778) -> Result<Option<BlockAccessList>, RpcErr> {
779    let block_hash = block.hash();
780    let commitment = block.header.block_access_list_hash;
781    if let Some(bal) = context.storage.get_block_access_list(block_hash)? {
782        // EIP-8159: never serve a BAL that doesn't match the header commitment.
783        // A stale/empty entry (e.g. from a prior regeneration against state that
784        // was later pruned) must degrade to "unavailable" rather than a wrong BAL.
785        if bal.matches_commitment(commitment, &NativeCrypto) {
786            return Ok(Some(bal));
787        }
788        warn!("Stored BAL for {block_hash} does not match header commitment; ignoring it");
789    }
790    let generated = context
791        .blockchain
792        .generate_bal_for_block(block)
793        .map_err(|e| RpcErr::Internal(e.to_string()))?;
794    // Only persist/serve a regenerated BAL if it matches the header commitment.
795    // Regeneration re-executes against the parent state; if that state is gone
796    // or stale the result can be empty/wrong, so guard before writing it back.
797    let regenerated = generated.is_some();
798    let Some(bal) = generated.filter(|bal| bal.matches_commitment(commitment, &NativeCrypto))
799    else {
800        // A successful regeneration whose hash doesn't match the commitment means
801        // the block was re-executed against wrong/incomplete state; don't serve or
802        // persist it. (Absent regeneration just means the state is unavailable.)
803        if regenerated {
804            warn!("Regenerated BAL for {block_hash} does not match header commitment; discarding");
805        }
806        return Ok(None);
807    };
808    // Write back so subsequent requests for this block are served from the
809    // store instead of re-executing every time.
810    if let Err(err) = context.storage.store_block_access_list(block_hash, &bal) {
811        warn!("Failed to persist regenerated block access list for {block_hash}: {err}");
812    }
813    Ok(Some(bal))
814}
815
816// ==================== V2 Body Methods (EIP-7928) ====================
817
818pub struct GetPayloadBodiesByHashV2Request {
819    pub hashes: Vec<BlockHash>,
820}
821
822impl RpcHandler for GetPayloadBodiesByHashV2Request {
823    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
824        let params = params
825            .as_ref()
826            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
827        if params.len() != 1 {
828            return Err(RpcErr::BadParams("Expected 1 param".to_owned()));
829        };
830
831        Ok(GetPayloadBodiesByHashV2Request {
832            hashes: serde_json::from_value(params[0].clone())?,
833        })
834    }
835
836    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
837        if self.hashes.len() as u64 > GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE {
838            return Err(RpcErr::TooLargeRequest);
839        }
840
841        let mut bodies: Vec<Option<ExecutionPayloadBodyV2>> = Vec::new();
842        for hash in &self.hashes {
843            let block = context.storage.get_block_by_hash(*hash).await?;
844            let result = match block {
845                Some(block) => {
846                    let bal = bal_for_block(&context, &block)?;
847                    Some(ExecutionPayloadBodyV2::from_body_with_bal(block.body, bal))
848                }
849                None => None,
850            };
851            bodies.push(result);
852        }
853
854        serde_json::to_value(bodies).map_err(|e| RpcErr::Internal(e.to_string()))
855    }
856}
857
858pub struct GetPayloadBodiesByRangeV2Request {
859    start: BlockNumber,
860    count: u64,
861}
862
863impl RpcHandler for GetPayloadBodiesByRangeV2Request {
864    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
865        let params = params
866            .as_ref()
867            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
868        if params.len() != 2 {
869            return Err(RpcErr::BadParams("Expected 2 params".to_owned()));
870        };
871        let start = parse_json_hex(&params[0]).map_err(|_| RpcErr::BadHexFormat(0))?;
872        let count = parse_json_hex(&params[1]).map_err(|_| RpcErr::BadHexFormat(1))?;
873        if start < 1 {
874            return Err(RpcErr::WrongParam("start".to_owned()));
875        }
876        if count < 1 {
877            return Err(RpcErr::WrongParam("count".to_owned()));
878        }
879        Ok(GetPayloadBodiesByRangeV2Request { start, count })
880    }
881
882    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
883        if self.count > GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE {
884            return Err(RpcErr::TooLargeRequest);
885        }
886        let latest_block_number = context.storage.get_latest_block_number().await?;
887        // NOTE: we truncate the range because the spec says we "MUST NOT return trailing
888        // null values if the request extends past the current latest known block"
889        let last = latest_block_number.min(self.start + self.count - 1);
890
891        // Bulk fetch bodies (like V1)
892        let block_bodies = context.storage.get_block_bodies(self.start, last).await?;
893
894        let mut bodies: Vec<Option<ExecutionPayloadBodyV2>> = Vec::new();
895        for (i, body_opt) in block_bodies.into_iter().enumerate() {
896            let block_number = self.start + i as u64;
897            let result = match body_opt {
898                Some(body) => {
899                    // Get header for this block
900                    let header =
901                        context
902                            .storage
903                            .get_block_header(block_number)?
904                            .ok_or_else(|| {
905                                RpcErr::Internal(format!(
906                                    "Header not found for block {block_number}"
907                                ))
908                            })?;
909                    let block = Block { header, body };
910
911                    let bal = bal_for_block(&context, &block)?;
912                    Some(ExecutionPayloadBodyV2::from_body_with_bal(block.body, bal))
913                }
914                None => None,
915            };
916            bodies.push(result);
917        }
918
919        serde_json::to_value(bodies).map_err(|e| RpcErr::Internal(e.to_string()))
920    }
921}
922
923fn parse_execution_payload(params: &Option<Vec<Value>>) -> Result<ExecutionPayload, RpcErr> {
924    let params = params
925        .as_ref()
926        .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
927    if params.len() != 1 {
928        return Err(RpcErr::BadParams("Expected 1 param".to_owned()));
929    }
930    serde_json::from_value(params[0].clone()).map_err(|_| RpcErr::WrongParam("payload".to_string()))
931}
932
933fn validate_execution_payload_v1(payload: &ExecutionPayload) -> Result<(), RpcErr> {
934    // Validate that only the required arguments are present
935    if payload.withdrawals.is_some() {
936        return Err(RpcErr::WrongParam("withdrawals".to_string()));
937    }
938    if payload.blob_gas_used.is_some() {
939        return Err(RpcErr::WrongParam("blob_gas_used".to_string()));
940    }
941    if payload.excess_blob_gas.is_some() {
942        return Err(RpcErr::WrongParam("excess_blob_gas".to_string()));
943    }
944
945    Ok(())
946}
947
948fn validate_execution_payload_v2(payload: &ExecutionPayload) -> Result<(), RpcErr> {
949    // Validate that only the required arguments are present
950    if payload.withdrawals.is_none() {
951        return Err(RpcErr::WrongParam("withdrawals".to_string()));
952    }
953    if payload.blob_gas_used.is_some() {
954        return Err(RpcErr::WrongParam("blob_gas_used".to_string()));
955    }
956    if payload.excess_blob_gas.is_some() {
957        return Err(RpcErr::WrongParam("excess_blob_gas".to_string()));
958    }
959
960    Ok(())
961}
962
963fn validate_execution_payload_v3(payload: &ExecutionPayload) -> Result<(), RpcErr> {
964    // Validate that only the required arguments are present
965    if payload.withdrawals.is_none() {
966        return Err(RpcErr::WrongParam("withdrawals".to_string()));
967    }
968    if payload.blob_gas_used.is_none() {
969        return Err(RpcErr::WrongParam("blob_gas_used".to_string()));
970    }
971    if payload.excess_blob_gas.is_none() {
972        return Err(RpcErr::WrongParam("excess_blob_gas".to_string()));
973    }
974
975    Ok(())
976}
977
978#[inline]
979fn validate_execution_payload_v4(payload: &ExecutionPayload) -> Result<(), RpcErr> {
980    // This method follows the same specification as `engine_newPayloadV4` additionally
981    // rejects payload without block access list
982
983    if payload.block_access_list.is_none() {
984        return Err(RpcErr::WrongParam("block_access_list".to_string()));
985    }
986
987    validate_execution_payload_v3(payload)?;
988
989    Ok(())
990}
991
992#[inline]
993fn validate_execution_payload_v5(payload: &ExecutionPayload) -> Result<(), RpcErr> {
994    validate_execution_payload_v4(payload)?;
995
996    if payload.slot_number.is_none() {
997        return Err(RpcErr::WrongParam("slot_number".to_string()));
998    }
999
1000    Ok(())
1001}
1002
1003fn validate_payload_v1_v2(block: &Block, context: &RpcApiContext) -> Result<(), RpcErr> {
1004    let chain_config = &context.storage.get_chain_config();
1005    if chain_config.is_cancun_activated(block.header.timestamp) {
1006        return Err(RpcErr::UnsupportedFork(
1007            "Cancun payload received".to_string(),
1008        ));
1009    }
1010    Ok(())
1011}
1012
1013// This function is used to make sure neither the current block nor its parent have been invalidated
1014async fn validate_ancestors(
1015    block: &Block,
1016    context: &RpcApiContext,
1017) -> Result<Option<PayloadStatus>, RpcErr> {
1018    // Check if the block has already been invalidated
1019    if let Some(latest_valid_hash) = context
1020        .storage
1021        .get_latest_valid_ancestor(block.hash())
1022        .await?
1023    {
1024        return Ok(Some(PayloadStatus::invalid_with(
1025            latest_valid_hash,
1026            "Header has been previously invalidated.".into(),
1027        )));
1028    }
1029
1030    // Check if the parent block has already been invalidated
1031    if let Some(latest_valid_hash) = context
1032        .storage
1033        .get_latest_valid_ancestor(block.header.parent_hash)
1034        .await?
1035    {
1036        // Invalidate child too
1037        context
1038            .storage
1039            .set_latest_valid_ancestor(block.header.hash(), latest_valid_hash)
1040            .await?;
1041        return Ok(Some(PayloadStatus::invalid_with(
1042            latest_valid_hash,
1043            "Parent header has been previously invalidated.".into(),
1044        )));
1045    }
1046
1047    Ok(None)
1048}
1049
1050async fn handle_new_payload_v1_v2(
1051    payload: &ExecutionPayload,
1052    block: Block,
1053    context: RpcApiContext,
1054    bal: Option<BlockAccessList>,
1055    make_witness: bool,
1056) -> Result<PayloadStatus, RpcErr> {
1057    let Some(syncer) = &context.syncer else {
1058        return Err(RpcErr::Internal(
1059            "New payload requested but syncer is not initialized".to_string(),
1060        ));
1061    };
1062    // Validate block hash
1063    if let Err(RpcErr::Internal(error_msg)) = validate_block_hash(payload, &block) {
1064        return Ok(PayloadStatus::invalid_with_err(&error_msg));
1065    }
1066
1067    // Check for invalid ancestors
1068    if let Some(status) = validate_ancestors(&block, &context).await? {
1069        return Ok(status);
1070    }
1071
1072    // We have validated ancestors, the parent is correct
1073    let latest_valid_hash = block.header.parent_hash;
1074
1075    if syncer.sync_mode() == SyncMode::Snap {
1076        debug!("Snap sync in progress, skipping new payload validation");
1077        return Ok(PayloadStatus::syncing());
1078    }
1079
1080    // All checks passed, execute payload
1081    let payload_status =
1082        try_execute_payload(block, &context, latest_valid_hash, bal, make_witness).await?;
1083    Ok(payload_status)
1084}
1085
1086async fn handle_new_payload_v3(
1087    payload: &ExecutionPayload,
1088    context: RpcApiContext,
1089    block: Block,
1090    expected_blob_versioned_hashes: Vec<H256>,
1091    bal: Option<BlockAccessList>,
1092    make_witness: bool,
1093) -> Result<PayloadStatus, RpcErr> {
1094    // V3 specific: validate blob hashes
1095    let blob_versioned_hashes: Vec<H256> = block
1096        .body
1097        .transactions
1098        .iter()
1099        .flat_map(|tx| tx.blob_versioned_hashes())
1100        .collect();
1101
1102    if expected_blob_versioned_hashes != blob_versioned_hashes {
1103        return Ok(PayloadStatus::invalid_with_err(
1104            "Invalid blob_versioned_hashes",
1105        ));
1106    }
1107
1108    handle_new_payload_v1_v2(payload, block, context, bal, make_witness).await
1109}
1110
1111async fn handle_new_payload_v4(
1112    payload: &ExecutionPayload,
1113    context: RpcApiContext,
1114    block: Block,
1115    expected_blob_versioned_hashes: Vec<H256>,
1116    bal: Option<BlockAccessList>,
1117    make_witness: bool,
1118) -> Result<PayloadStatus, RpcErr> {
1119    if let Some(bal) = &bal
1120        && let Err(err) = bal.validate_ordering()
1121    {
1122        return Ok(PayloadStatus::invalid_with_err(&err));
1123    }
1124    handle_new_payload_v3(
1125        payload,
1126        context,
1127        block,
1128        expected_blob_versioned_hashes,
1129        bal,
1130        make_witness,
1131    )
1132    .await
1133}
1134
1135// Elements of the list MUST be ordered by request_type in ascending order.
1136// Elements with empty request_data MUST be excluded from the list.
1137fn validate_execution_requests(execution_requests: &[EncodedRequests]) -> Result<(), RpcErr> {
1138    let mut last_type: i32 = -1;
1139    for requests in execution_requests {
1140        if requests.0.len() < 2 {
1141            return Err(RpcErr::WrongParam("Empty requests data.".to_string()));
1142        }
1143        let request_type = requests.0[0] as i32;
1144        if last_type >= request_type {
1145            return Err(RpcErr::WrongParam("Invalid requests order.".to_string()));
1146        }
1147        last_type = request_type;
1148    }
1149    Ok(())
1150}
1151
1152fn get_block_from_payload(
1153    payload: &ExecutionPayload,
1154    parent_beacon_block_root: Option<H256>,
1155    requests_hash: Option<H256>,
1156    block_access_list_hash: Option<H256>,
1157) -> Result<Block, RLPDecodeError> {
1158    let block_hash = payload.block_hash;
1159    let block_number = payload.block_number;
1160    debug!(%block_hash, %block_number, "Received new payload");
1161
1162    payload.clone().into_block(
1163        parent_beacon_block_root,
1164        requests_hash,
1165        block_access_list_hash,
1166    )
1167}
1168
1169fn validate_block_hash(payload: &ExecutionPayload, block: &Block) -> Result<(), RpcErr> {
1170    let block_hash = payload.block_hash;
1171    let actual_block_hash = block.hash();
1172    if block_hash != actual_block_hash {
1173        return Err(RpcErr::Internal(format!(
1174            "Invalid block hash. Expected {actual_block_hash:#x}, got {block_hash:#x}"
1175        )));
1176    }
1177    Ok(())
1178}
1179
1180pub async fn add_block(
1181    ctx: &RpcApiContext,
1182    block: Block,
1183    bal: Option<BlockAccessList>,
1184    make_witness: bool,
1185) -> Result<Option<ExecutionWitness>, ChainError> {
1186    let (notify_send, notify_recv) = oneshot::channel();
1187    ctx.block_worker_channel
1188        .send((notify_send, block, bal, make_witness))
1189        .map_err(|e| {
1190            ChainError::Custom(format!(
1191                "failed to send block execution request to worker: {e}"
1192            ))
1193        })?;
1194    notify_recv
1195        .await
1196        .map_err(|e| ChainError::Custom(format!("failed to receive block execution result: {e}")))?
1197}
1198
1199async fn try_execute_payload(
1200    block: Block,
1201    context: &RpcApiContext,
1202    latest_valid_hash: H256,
1203    bal: Option<BlockAccessList>,
1204    make_witness: bool,
1205) -> Result<PayloadStatus, RpcErr> {
1206    let Some(syncer) = &context.syncer else {
1207        return Err(RpcErr::Internal(
1208            "New payload requested but syncer is not initialized".to_string(),
1209        ));
1210    };
1211    let block_hash = block.hash();
1212    let block_number = block.header.number;
1213    let storage = &context.storage;
1214    // If we already know this block, return valid without re-importing it.
1215    // Witness requests still need to include a witness in the response.
1216    // We check for header only as we do not download the block bodies before the pivot during snap sync
1217    // https://github.com/lambdaclass/ethrex/issues/1766
1218    if storage.get_block_header_by_hash(block_hash)?.is_some() {
1219        return payload_status_for_existing_block(&block, context, make_witness).await;
1220    }
1221
1222    // A payload whose parent *state* we don't have yet must be answered with
1223    // SYNCING, never INVALID: without the parent state we cannot validate it,
1224    // so we must not declare it invalid. This happens after a restart, when
1225    // state regeneration hasn't caught up to the CL head, or when the CL sends a
1226    // newPayload for a block beyond our current state. Without this guard,
1227    // execution fails with `EvmError::DB("state root missing")` and gets mapped
1228    // to INVALID below, wrongly poisoning the CL's view of a valid block (and
1229    // persisting it via `set_latest_valid_ancestor`). The parent block being
1230    // entirely absent is handled as `ParentNotFound` by `add_block` below.
1231    if let Some(parent_header) = storage.get_block_header_by_hash(block.header.parent_hash)?
1232        && !storage.has_state_root(parent_header.state_root)?
1233    {
1234        debug!(%block_hash, %block_number, "Parent state missing, returning SYNCING and triggering sync");
1235        syncer.sync_to_head(block_hash);
1236        return Ok(PayloadStatus::syncing());
1237    }
1238
1239    // Execute and store the block
1240    debug!(%block_hash, %block_number, "Executing payload");
1241
1242    match add_block(context, block, bal, make_witness).await {
1243        Err(ChainError::ParentNotFound) => {
1244            // Start sync
1245            syncer.sync_to_head(block_hash);
1246            Ok(PayloadStatus::syncing())
1247        }
1248        // Parent block is present but its state isn't available yet (e.g. state
1249        // regeneration after a restart hasn't reached the CL head). This is a
1250        // SYNCING condition, not an error and not INVALID: trigger a sync and
1251        // report SYNCING so the CL keeps the (valid) block.
1252        Err(ChainError::ParentStateNotFound) => {
1253            debug!(%block_hash, "Parent state not found, returning SYNCING and triggering sync");
1254            syncer.sync_to_head(block_hash);
1255            Ok(PayloadStatus::syncing())
1256        }
1257        Err(ChainError::InvalidBlock(error)) => {
1258            warn!(%block_hash, %block_number, "Error executing block: {error}");
1259            context
1260                .storage
1261                .set_latest_valid_ancestor(block_hash, latest_valid_hash)
1262                .await?;
1263            Ok(PayloadStatus::invalid_with(
1264                latest_valid_hash,
1265                error.to_string(),
1266            ))
1267        }
1268        Err(ChainError::EvmError(error)) => {
1269            warn!(%block_hash, %block_number, "Error executing block: {error}");
1270            context
1271                .storage
1272                .set_latest_valid_ancestor(block_hash, latest_valid_hash)
1273                .await?;
1274            Ok(PayloadStatus::invalid_with(
1275                latest_valid_hash,
1276                error.to_string(),
1277            ))
1278        }
1279        Err(ChainError::StoreError(error)) => {
1280            warn!(%block_hash, %block_number, "Error storing block: {error}");
1281            Err(RpcErr::Internal(error.to_string()))
1282        }
1283        Err(e) => {
1284            error!("{e} for block {block_hash}");
1285            Err(RpcErr::Internal(e.to_string()))
1286        }
1287        Ok(witness) => {
1288            debug!("Block with hash {block_hash} executed and added to storage successfully");
1289            let mut status = PayloadStatus::valid_with_hash(block_hash);
1290            if make_witness {
1291                let witness = witness.ok_or_else(|| {
1292                    RpcErr::Internal("Payload executed without producing a witness".to_string())
1293                })?;
1294                status.witness = Some(encode_witness_for_engine_rpc(witness)?);
1295            }
1296            Ok(status)
1297        }
1298    }
1299}
1300
1301async fn payload_status_for_existing_block(
1302    block: &Block,
1303    context: &RpcApiContext,
1304    make_witness: bool,
1305) -> Result<PayloadStatus, RpcErr> {
1306    let block_hash = block.hash();
1307    let mut status = PayloadStatus::valid_with_hash(block_hash);
1308
1309    if make_witness {
1310        status.witness = Some(witness_for_existing_block(block, context).await?);
1311    }
1312
1313    Ok(status)
1314}
1315
1316async fn witness_for_existing_block(
1317    block: &Block,
1318    context: &RpcApiContext,
1319) -> Result<Bytes, RpcErr> {
1320    let block_hash = block.hash();
1321    if let Some(json_bytes) = context
1322        .storage
1323        .get_witness_json_bytes(block.header.number, block_hash)?
1324    {
1325        let rpc_witness = serde_json::from_slice(&json_bytes).map_err(|error| {
1326            RpcErr::Internal(format!("Failed to parse cached witness: {error}"))
1327        })?;
1328        return encode_rpc_witness_for_engine_rpc(rpc_witness);
1329    }
1330
1331    let witness = context
1332        .blockchain
1333        .generate_witness_for_blocks(std::slice::from_ref(block))
1334        .await
1335        .map_err(|error| RpcErr::Internal(format!("Failed to build execution witness: {error}")))?;
1336    encode_witness_for_engine_rpc(witness)
1337}
1338
1339fn encode_witness_for_engine_rpc(witness: ExecutionWitness) -> Result<Bytes, RpcErr> {
1340    let rpc_witness = RpcExecutionWitness::try_from(witness).map_err(|error| {
1341        RpcErr::Internal(format!("Failed to encode execution witness: {error}"))
1342    })?;
1343    encode_rpc_witness_for_engine_rpc(rpc_witness)
1344}
1345
1346/// Encodes the witness in geth's opaque `engine_newPayloadWithWitness*` shape.
1347///
1348/// Format: geth returns `rlp.EncodeToBytes(proofs)` from `newPayload`, and
1349/// `stateless.Witness::EncodeRLP` delegates to [`ExtWitness`] — see its docs
1350/// for the shape and geth references.
1351/// Additional references:
1352/// https://github.com/ethereum/go-ethereum/blob/4daaaadfc4706b0a49d4dfde3559de7be968c28a/core/stateless/encoding.go#L92-L98
1353/// https://github.com/ethereum/go-ethereum/blob/4daaaadfc4706b0a49d4dfde3559de7be968c28a/eth/catalyst/api.go#L915-L920
1354fn encode_rpc_witness_for_engine_rpc(rpc_witness: RpcExecutionWitness) -> Result<Bytes, RpcErr> {
1355    let mut headers = rpc_witness
1356        .headers
1357        .iter()
1358        .map(|header| BlockHeader::decode(header.as_ref()))
1359        .collect::<Result<Vec<_>, _>>()
1360        .map_err(|error| RpcErr::Internal(format!("Failed to decode witness header: {error}")))?;
1361    headers.sort_by_key(|header| header.number);
1362    let mut codes = rpc_witness.codes;
1363    codes.sort_by(|a, b| a.as_ref().cmp(b.as_ref()));
1364    let mut state = rpc_witness.state;
1365    state.sort_by(|a, b| a.as_ref().cmp(b.as_ref()));
1366    let mut keys = rpc_witness.keys;
1367    keys.sort_by(|a, b| a.as_ref().cmp(b.as_ref()));
1368    let ext_witness = ExtWitness {
1369        headers,
1370        codes,
1371        state,
1372        keys,
1373    };
1374    Ok(Bytes::from(ext_witness.encode_to_vec()))
1375}
1376
1377fn parse_get_payload_request(params: &Option<Vec<Value>>) -> Result<u64, RpcErr> {
1378    let params = params
1379        .as_ref()
1380        .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
1381    if params.len() != 1 {
1382        return Err(RpcErr::BadParams("Expected 1 param".to_owned()));
1383    };
1384    let Ok(hex_str) = serde_json::from_value::<String>(params[0].clone()) else {
1385        return Err(RpcErr::BadParams(
1386            "Expected param to be a string".to_owned(),
1387        ));
1388    };
1389    // Check that the hex string is 0x prefixed
1390    let Some(hex_str) = hex_str.strip_prefix("0x") else {
1391        return Err(RpcErr::BadHexFormat(0));
1392    };
1393    // Parse hex string
1394    let Ok(payload_id) = u64::from_str_radix(hex_str, 16) else {
1395        return Err(RpcErr::BadHexFormat(0));
1396    };
1397    Ok(payload_id)
1398}
1399
1400fn validate_fork(block: &Block, fork: Fork, context: &RpcApiContext) -> Result<(), RpcErr> {
1401    // Check timestamp matches valid fork
1402    let chain_config = &context.storage.get_chain_config();
1403    let current_fork = chain_config.get_fork(block.header.timestamp);
1404
1405    if current_fork != fork {
1406        return Err(RpcErr::UnsupportedFork(format!("{current_fork:?}")));
1407    }
1408    Ok(())
1409}
1410
1411async fn get_payload(payload_id: u64, context: &RpcApiContext) -> Result<PayloadBundle, RpcErr> {
1412    info!(
1413        id = %format!("{:#018x}", payload_id),
1414        "Requested payload with"
1415    );
1416    let (blobs_bundle, requests, block_value, block, block_access_list) = {
1417        let PayloadBuildResult {
1418            blobs_bundle,
1419            block_value,
1420            requests,
1421            payload,
1422            block_access_list,
1423            ..
1424        } = context
1425            .blockchain
1426            .get_payload(payload_id)
1427            .await
1428            .map_err(|err| match err {
1429                ChainError::UnknownPayload => {
1430                    RpcErr::UnknownPayload(format!("Payload with id {payload_id:#018x} not found",))
1431                }
1432                err => RpcErr::Internal(err.to_string()),
1433            })?;
1434        (
1435            blobs_bundle,
1436            requests,
1437            block_value,
1438            payload,
1439            block_access_list,
1440        )
1441    };
1442
1443    let new_payload = PayloadBundle {
1444        block,
1445        block_value,
1446        blobs_bundle,
1447        requests,
1448        block_access_list,
1449    };
1450
1451    Ok(new_payload)
1452}
1453
1454#[cfg(test)]
1455mod tests {
1456    use super::*;
1457    use crate::test_utils::default_context_with_storage;
1458    use ethrex_common::types::ChainConfig;
1459    use ethrex_rlp::encode::RLPEncode;
1460    use ethrex_storage::{EngineType, Store};
1461
1462    fn header(number: u64) -> BlockHeader {
1463        BlockHeader {
1464            number,
1465            ..Default::default()
1466        }
1467    }
1468
1469    fn v5_payload() -> ExecutionPayload {
1470        ExecutionPayload {
1471            parent_hash: H256::zero(),
1472            fee_recipient: Default::default(),
1473            state_root: H256::zero(),
1474            receipts_root: H256::zero(),
1475            logs_bloom: Default::default(),
1476            prev_randao: H256::zero(),
1477            block_number: 0,
1478            gas_limit: 30_000_000,
1479            gas_used: 0,
1480            timestamp: 0,
1481            extra_data: Bytes::new(),
1482            base_fee_per_gas: 1,
1483            block_hash: H256::zero(),
1484            transactions: vec![],
1485            withdrawals: Some(vec![]),
1486            blob_gas_used: Some(0),
1487            excess_blob_gas: Some(0),
1488            slot_number: Some(0),
1489            block_access_list: Some(BlockAccessList::default()),
1490        }
1491    }
1492
1493    #[test]
1494    fn new_payload_with_witness_v5_parses_like_v5() {
1495        let params = Some(vec![
1496            serde_json::json!(v5_payload()),
1497            serde_json::json!(Vec::<H256>::new()),
1498            serde_json::json!(H256::zero()),
1499            serde_json::json!(Vec::<EncodedRequests>::new()),
1500        ]);
1501
1502        let request = NewPayloadWithWitnessV5Request::parse(&params).unwrap();
1503
1504        assert_eq!(request.0.payload.slot_number, Some(0));
1505        assert!(request.0.raw_bal_hash.is_some());
1506    }
1507
1508    #[test]
1509    fn new_payload_v5_rejects_missing_slot_number() {
1510        let mut payload = v5_payload();
1511        payload.slot_number = None;
1512
1513        let err = validate_execution_payload_v5(&payload).unwrap_err();
1514
1515        assert!(matches!(err, RpcErr::WrongParam(param) if param == "slot_number"));
1516    }
1517
1518    #[test]
1519    fn engine_witness_encoding_matches_geth_ext_witness_shape() {
1520        let header_1 = header(1);
1521        let header_2 = header(2);
1522        let witness = RpcExecutionWitness {
1523            headers: vec![
1524                header_2.encode_to_vec().into(),
1525                header_1.encode_to_vec().into(),
1526            ],
1527            codes: vec![
1528                Bytes::from_static(&[0x02]),
1529                Bytes::from_static(&[0x01, 0xff]),
1530                Bytes::from_static(&[0x01]),
1531            ],
1532            state: vec![
1533                Bytes::from_static(&[0xff]),
1534                Bytes::from_static(&[0x00]),
1535                Bytes::from_static(&[0x7f]),
1536            ],
1537            keys: vec![Bytes::from_static(&[0x03]), Bytes::from_static(&[0x02])],
1538        };
1539
1540        let encoded = encode_rpc_witness_for_engine_rpc(witness).unwrap();
1541
1542        let expected_headers = vec![header_1, header_2];
1543        let expected_codes = vec![
1544            Bytes::from_static(&[0x01]),
1545            Bytes::from_static(&[0x01, 0xff]),
1546            Bytes::from_static(&[0x02]),
1547        ];
1548        let expected_state = vec![
1549            Bytes::from_static(&[0x00]),
1550            Bytes::from_static(&[0x7f]),
1551            Bytes::from_static(&[0xff]),
1552        ];
1553        let expected_keys = vec![Bytes::from_static(&[0x02]), Bytes::from_static(&[0x03])];
1554        let expected = (
1555            expected_headers,
1556            expected_codes,
1557            expected_state,
1558            expected_keys,
1559        )
1560            .encode_to_vec();
1561
1562        assert_eq!(encoded.as_ref(), expected.as_slice());
1563    }
1564
1565    #[test]
1566    fn engine_witness_encoding_from_execution_witness_matches_expected_bytes() {
1567        let header_1 = header(1);
1568        let header_2 = header(2);
1569        let witness = ExecutionWitness {
1570            codes: vec![vec![0x02], vec![0x01]],
1571            block_headers_bytes: vec![header_2.encode_to_vec(), header_1.encode_to_vec()],
1572            first_block_number: 1,
1573            chain_config: ChainConfig::default(),
1574            state_trie_root: None,
1575            storage_trie_roots: Default::default(),
1576        };
1577
1578        let encoded = encode_witness_for_engine_rpc(witness).unwrap();
1579
1580        let expected = (
1581            vec![header_1, header_2],
1582            vec![Bytes::from_static(&[0x01]), Bytes::from_static(&[0x02])],
1583            Vec::<Bytes>::new(),
1584            Vec::<Bytes>::new(),
1585        )
1586            .encode_to_vec();
1587        assert_eq!(encoded.as_ref(), expected.as_slice());
1588    }
1589
1590    async fn test_context() -> RpcApiContext {
1591        let storage = Store::new("test-payload-bodies", EngineType::InMemory)
1592            .expect("Failed to create test store");
1593        default_context_with_storage(storage).await
1594    }
1595
1596    #[tokio::test]
1597    async fn get_payload_bodies_by_hash_v1_accepts_exactly_max_size() {
1598        // Spec: clients MUST support request sizes of at least the max constant, so
1599        // exactly MAX must be served, not rejected.
1600        let request = GetPayloadBodiesByHashV1Request {
1601            hashes: vec![BlockHash::default(); GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE as usize],
1602        };
1603        let result = request.handle(test_context().await).await;
1604        assert!(!matches!(result, Err(RpcErr::TooLargeRequest)));
1605    }
1606
1607    #[tokio::test]
1608    async fn get_payload_bodies_by_hash_v1_rejects_above_max_size() {
1609        let request = GetPayloadBodiesByHashV1Request {
1610            hashes: vec![BlockHash::default(); GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE as usize + 1],
1611        };
1612        let result = request.handle(test_context().await).await;
1613        assert!(matches!(result, Err(RpcErr::TooLargeRequest)));
1614    }
1615
1616    #[tokio::test]
1617    async fn get_payload_bodies_by_range_v1_accepts_exactly_max_size() {
1618        let request = GetPayloadBodiesByRangeV1Request {
1619            start: 1,
1620            count: GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE,
1621        };
1622        let result = request.handle(test_context().await).await;
1623        assert!(!matches!(result, Err(RpcErr::TooLargeRequest)));
1624    }
1625
1626    #[tokio::test]
1627    async fn get_payload_bodies_by_range_v1_rejects_above_max_size() {
1628        let request = GetPayloadBodiesByRangeV1Request {
1629            start: 1,
1630            count: GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE + 1,
1631        };
1632        let result = request.handle(test_context().await).await;
1633        assert!(matches!(result, Err(RpcErr::TooLargeRequest)));
1634    }
1635
1636    #[tokio::test]
1637    async fn get_payload_bodies_by_hash_v2_accepts_exactly_max_size() {
1638        let request = GetPayloadBodiesByHashV2Request {
1639            hashes: vec![BlockHash::default(); (GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE) as usize],
1640        };
1641        let result = request.handle(test_context().await).await;
1642        assert!(!matches!(result, Err(RpcErr::TooLargeRequest)));
1643    }
1644
1645    #[tokio::test]
1646    async fn get_payload_bodies_by_hash_v2_rejects_above_max_size() {
1647        let request = GetPayloadBodiesByHashV2Request {
1648            hashes: vec![BlockHash::default(); (GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE + 1) as usize],
1649        };
1650        let result = request.handle(test_context().await).await;
1651        assert!(matches!(result, Err(RpcErr::TooLargeRequest)));
1652    }
1653
1654    #[tokio::test]
1655    async fn get_payload_bodies_by_range_v2_accepts_exactly_max_size() {
1656        let request = GetPayloadBodiesByRangeV2Request {
1657            start: 1,
1658            count: GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE,
1659        };
1660        let result = request.handle(test_context().await).await;
1661        assert!(!matches!(result, Err(RpcErr::TooLargeRequest)));
1662    }
1663
1664    #[tokio::test]
1665    async fn get_payload_bodies_by_range_v2_rejects_above_max_size() {
1666        let request = GetPayloadBodiesByRangeV2Request {
1667            start: 1,
1668            count: GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE + 1,
1669        };
1670        let result = request.handle(test_context().await).await;
1671        assert!(matches!(result, Err(RpcErr::TooLargeRequest)));
1672    }
1673}