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