1use crate::{
2 capabilities::EngineCapabilities, metrics::EngineApiMetrics, EngineApiError, EngineApiResult,
3};
4use alloy_eips::{
5 eip1898::BlockHashOrNumber,
6 eip4844::{BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1},
7 eip4895::Withdrawals,
8 eip7685::RequestsOrHash,
9};
10use alloy_primitives::{BlockHash, BlockNumber, Bytes, Sealable, B128, B256, U64};
11use alloy_rpc_types_engine::{
12 CancunPayloadFields, ClientVersionV1, ExecutionData, ExecutionPayloadBodiesV1,
13 ExecutionPayloadBodiesV2, ExecutionPayloadBodyV1, ExecutionPayloadBodyV2,
14 ExecutionPayloadInputV2, ExecutionPayloadSidecar, ExecutionPayloadV1, ExecutionPayloadV3,
15 ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus,
16 PraguePayloadFields,
17};
18use async_trait::async_trait;
19use jsonrpsee_core::{server::RpcModule, RpcResult};
20use reth_chainspec::EthereumHardforks;
21use reth_engine_primitives::{ConsensusEngineHandle, EngineApiValidator, EngineTypes};
22use reth_network_api::{CellCustody, NetworkInfo};
23use reth_payload_builder::PayloadStore;
24use reth_payload_primitives::{
25 validate_payload_timestamp, EngineApiMessageVersion, MessageValidationKind,
26 PayloadOrAttributes, PayloadTypes,
27};
28use reth_primitives_traits::{Block, BlockBody};
29use reth_rpc_api::{EngineApiServer, IntoEngineApiRpcModule};
30use reth_storage_api::{BalProvider, BlockReader, HeaderProvider, StateProviderFactory};
31use reth_tasks::Runtime;
32use reth_transaction_pool::TransactionPool;
33use std::{
34 sync::Arc,
35 time::{Instant, SystemTime},
36};
37use tokio::sync::oneshot;
38use tracing::{debug, trace, warn};
39
40pub type EngineApiSender<Ok> = oneshot::Sender<EngineApiResult<Ok>>;
42
43const MAX_PAYLOAD_BODIES_LIMIT: u64 = 1024;
45
46const MAX_BLOB_LIMIT: usize = 128;
48
49pub struct EngineApi<Provider, PayloadT: PayloadTypes, Pool, Validator, ChainSpec> {
65 inner: Arc<EngineApiInner<Provider, PayloadT, Pool, Validator, ChainSpec>>,
66}
67
68impl<Provider, PayloadT: PayloadTypes, Pool, Validator, ChainSpec>
69 EngineApi<Provider, PayloadT, Pool, Validator, ChainSpec>
70{
71 pub fn chain_spec(&self) -> &Arc<ChainSpec> {
73 &self.inner.chain_spec
74 }
75
76 pub fn client_version(&self) -> &ClientVersionV1 {
78 &self.inner.client
79 }
80}
81
82impl<Provider, PayloadT, Pool, Validator, ChainSpec>
83 EngineApi<Provider, PayloadT, Pool, Validator, ChainSpec>
84where
85 Provider: HeaderProvider + BlockReader + StateProviderFactory + BalProvider + 'static,
86 PayloadT: PayloadTypes,
87 Pool: TransactionPool + 'static,
88 Validator: EngineApiValidator<PayloadT>,
89 ChainSpec: EthereumHardforks + Send + Sync + 'static,
90{
91 #[expect(clippy::too_many_arguments)]
93 pub fn new(
94 provider: Provider,
95 chain_spec: Arc<ChainSpec>,
96 beacon_consensus: ConsensusEngineHandle<PayloadT>,
97 payload_store: PayloadStore<PayloadT>,
98 tx_pool: Pool,
99 task_spawner: Runtime,
100 client: ClientVersionV1,
101 capabilities: EngineCapabilities,
102 validator: Validator,
103 accept_execution_requests_hash: bool,
104 network: impl NetworkInfo + 'static,
105 ) -> Self {
106 let cell_custody = network.cell_custody().clone();
107 let is_syncing = Arc::new(move || network.is_syncing());
108 let inner = Arc::new(EngineApiInner {
109 provider,
110 chain_spec,
111 beacon_consensus,
112 payload_store,
113 task_spawner,
114 metrics: EngineApiMetrics::default(),
115 client,
116 capabilities,
117 tx_pool,
118 validator,
119 accept_execution_requests_hash,
120 cell_custody,
121 is_syncing,
122 });
123 Self { inner }
124 }
125
126 pub fn get_client_version_v1(
128 &self,
129 _client: ClientVersionV1,
130 ) -> EngineApiResult<Vec<ClientVersionV1>> {
131 Ok(vec![self.inner.client.clone()])
132 }
133
134 async fn get_payload_timestamp(&self, payload_id: PayloadId) -> EngineApiResult<u64> {
136 Ok(self
137 .inner
138 .payload_store
139 .payload_timestamp(payload_id)
140 .await
141 .ok_or(EngineApiError::UnknownPayload)??)
142 }
143
144 pub async fn new_payload_v1(
147 &self,
148 payload: PayloadT::ExecutionData,
149 ) -> EngineApiResult<PayloadStatus> {
150 let payload_or_attrs = PayloadOrAttributes::<
151 '_,
152 PayloadT::ExecutionData,
153 PayloadT::PayloadAttributes,
154 >::from_execution_payload(&payload);
155
156 self.inner
157 .validator
158 .validate_version_specific_fields(EngineApiMessageVersion::V1, payload_or_attrs)?;
159
160 Ok(self.inner.beacon_consensus.new_payload(payload).await?)
161 }
162
163 pub async fn new_payload_v1_metered(
165 &self,
166 payload: PayloadT::ExecutionData,
167 ) -> EngineApiResult<PayloadStatus> {
168 let start = Instant::now();
169 let res = Self::new_payload_v1(self, payload).await;
170 let elapsed = start.elapsed();
171 self.inner.metrics.latency.new_payload_v1.record(elapsed);
172 res
173 }
174
175 pub async fn new_payload_v2(
177 &self,
178 payload: PayloadT::ExecutionData,
179 ) -> EngineApiResult<PayloadStatus> {
180 let payload_or_attrs = PayloadOrAttributes::<
181 '_,
182 PayloadT::ExecutionData,
183 PayloadT::PayloadAttributes,
184 >::from_execution_payload(&payload);
185 self.inner
186 .validator
187 .validate_version_specific_fields(EngineApiMessageVersion::V2, payload_or_attrs)?;
188 Ok(self.inner.beacon_consensus.new_payload(payload).await?)
189 }
190
191 pub async fn new_payload_v2_metered(
193 &self,
194 payload: PayloadT::ExecutionData,
195 ) -> EngineApiResult<PayloadStatus> {
196 let start = Instant::now();
197 let res = Self::new_payload_v2(self, payload).await;
198 let elapsed = start.elapsed();
199 self.inner.metrics.latency.new_payload_v2.record(elapsed);
200 res
201 }
202
203 pub async fn new_payload_v3(
205 &self,
206 payload: PayloadT::ExecutionData,
207 ) -> EngineApiResult<PayloadStatus> {
208 let payload_or_attrs = PayloadOrAttributes::<
209 '_,
210 PayloadT::ExecutionData,
211 PayloadT::PayloadAttributes,
212 >::from_execution_payload(&payload);
213 self.inner
214 .validator
215 .validate_version_specific_fields(EngineApiMessageVersion::V3, payload_or_attrs)?;
216
217 Ok(self.inner.beacon_consensus.new_payload(payload).await?)
218 }
219
220 pub async fn new_payload_v3_metered(
222 &self,
223 payload: PayloadT::ExecutionData,
224 ) -> RpcResult<PayloadStatus> {
225 let start = Instant::now();
226
227 let res = Self::new_payload_v3(self, payload).await;
228 let elapsed = start.elapsed();
229 self.inner.metrics.latency.new_payload_v3.record(elapsed);
230 Ok(res?)
231 }
232
233 pub async fn new_payload_v4(
235 &self,
236 payload: PayloadT::ExecutionData,
237 ) -> EngineApiResult<PayloadStatus> {
238 let payload_or_attrs = PayloadOrAttributes::<
239 '_,
240 PayloadT::ExecutionData,
241 PayloadT::PayloadAttributes,
242 >::from_execution_payload(&payload);
243 self.inner
244 .validator
245 .validate_version_specific_fields(EngineApiMessageVersion::V4, payload_or_attrs)?;
246
247 Ok(self.inner.beacon_consensus.new_payload(payload).await?)
248 }
249
250 pub async fn new_payload_v4_metered(
252 &self,
253 payload: PayloadT::ExecutionData,
254 ) -> RpcResult<PayloadStatus> {
255 let start = Instant::now();
256 let res = Self::new_payload_v4(self, payload).await;
257
258 let elapsed = start.elapsed();
259 self.inner.metrics.latency.new_payload_v4.record(elapsed);
260 Ok(res?)
261 }
262
263 pub async fn new_payload_v5(
269 &self,
270 payload: PayloadT::ExecutionData,
271 ) -> EngineApiResult<PayloadStatus> {
272 let payload_or_attrs = PayloadOrAttributes::<
273 '_,
274 PayloadT::ExecutionData,
275 PayloadT::PayloadAttributes,
276 >::from_execution_payload(&payload);
277 self.inner
278 .validator
279 .validate_version_specific_fields(EngineApiMessageVersion::V5, payload_or_attrs)?;
280 Ok(self.inner.beacon_consensus.new_payload(payload).await?)
281 }
282
283 pub async fn new_payload_v5_metered(
285 &self,
286 payload: PayloadT::ExecutionData,
287 ) -> RpcResult<PayloadStatus> {
288 let start = Instant::now();
289 let res = Self::new_payload_v5(self, payload).await;
290 let elapsed = start.elapsed();
291 self.inner.metrics.latency.new_payload_v5.record(elapsed);
292 Ok(res?)
293 }
294
295 pub fn accept_execution_requests_hash(&self) -> bool {
297 self.inner.accept_execution_requests_hash
298 }
299}
300
301impl<Provider, EngineT, Pool, Validator, ChainSpec>
302 EngineApi<Provider, EngineT, Pool, Validator, ChainSpec>
303where
304 Provider: HeaderProvider + BlockReader + StateProviderFactory + BalProvider + 'static,
305 EngineT: EngineTypes,
306 Pool: TransactionPool + 'static,
307 Validator: EngineApiValidator<EngineT>,
308 ChainSpec: EthereumHardforks + Send + Sync + 'static,
309{
310 pub async fn fork_choice_updated_v1(
317 &self,
318 state: ForkchoiceState,
319 payload_attrs: Option<EngineT::PayloadAttributes>,
320 ) -> EngineApiResult<ForkchoiceUpdated> {
321 self.validate_and_execute_forkchoice(EngineApiMessageVersion::V1, state, payload_attrs)
322 .await
323 }
324
325 pub async fn fork_choice_updated_v1_metered(
327 &self,
328 state: ForkchoiceState,
329 payload_attrs: Option<EngineT::PayloadAttributes>,
330 ) -> EngineApiResult<ForkchoiceUpdated> {
331 let start = Instant::now();
332 let res = Self::fork_choice_updated_v1(self, state, payload_attrs).await;
333 self.inner.metrics.latency.fork_choice_updated_v1.record(start.elapsed());
334 res
335 }
336
337 pub async fn fork_choice_updated_v2(
342 &self,
343 state: ForkchoiceState,
344 payload_attrs: Option<EngineT::PayloadAttributes>,
345 ) -> EngineApiResult<ForkchoiceUpdated> {
346 self.validate_and_execute_forkchoice(EngineApiMessageVersion::V2, state, payload_attrs)
347 .await
348 }
349
350 pub async fn fork_choice_updated_v2_metered(
352 &self,
353 state: ForkchoiceState,
354 payload_attrs: Option<EngineT::PayloadAttributes>,
355 ) -> EngineApiResult<ForkchoiceUpdated> {
356 let start = Instant::now();
357 let res = Self::fork_choice_updated_v2(self, state, payload_attrs).await;
358 self.inner.metrics.latency.fork_choice_updated_v2.record(start.elapsed());
359 res
360 }
361
362 pub async fn fork_choice_updated_v3(
367 &self,
368 state: ForkchoiceState,
369 payload_attrs: Option<EngineT::PayloadAttributes>,
370 ) -> EngineApiResult<ForkchoiceUpdated> {
371 self.validate_and_execute_forkchoice(EngineApiMessageVersion::V3, state, payload_attrs)
372 .await
373 }
374
375 pub async fn fork_choice_updated_v3_metered(
377 &self,
378 state: ForkchoiceState,
379 payload_attrs: Option<EngineT::PayloadAttributes>,
380 ) -> EngineApiResult<ForkchoiceUpdated> {
381 let start = Instant::now();
382 let res = Self::fork_choice_updated_v3(self, state, payload_attrs).await;
383 self.inner.metrics.latency.fork_choice_updated_v3.record(start.elapsed());
384 res
385 }
386
387 pub async fn fork_choice_updated_v4(
392 &self,
393 state: ForkchoiceState,
394 payload_attrs: Option<EngineT::PayloadAttributes>,
395 custody_columns: Option<B128>,
396 ) -> EngineApiResult<ForkchoiceUpdated> {
397 if let Some(custody_columns) = custody_columns {
398 self.inner.cell_custody.set(custody_columns);
399 }
400 self.validate_and_execute_forkchoice(EngineApiMessageVersion::V4, state, payload_attrs)
401 .await
402 }
403
404 pub async fn fork_choice_updated_v4_metered(
406 &self,
407 state: ForkchoiceState,
408 payload_attrs: Option<EngineT::PayloadAttributes>,
409 custody_columns: Option<B128>,
410 ) -> EngineApiResult<ForkchoiceUpdated> {
411 let start = Instant::now();
412 let res = Self::fork_choice_updated_v4(self, state, payload_attrs, custody_columns).await;
413 self.inner.metrics.latency.fork_choice_updated_v4.record(start.elapsed());
414 res
415 }
416
417 async fn get_built_payload(
419 &self,
420 payload_id: PayloadId,
421 ) -> EngineApiResult<EngineT::BuiltPayload> {
422 self.inner
423 .payload_store
424 .resolve(payload_id)
425 .await
426 .ok_or(EngineApiError::UnknownPayload)?
427 .map_err(|_| EngineApiError::UnknownPayload)
428 }
429
430 async fn get_payload_inner<R>(
433 &self,
434 payload_id: PayloadId,
435 version: EngineApiMessageVersion,
436 ) -> EngineApiResult<R>
437 where
438 EngineT::BuiltPayload: TryInto<R>,
439 {
440 let timestamp = self.get_payload_timestamp(payload_id).await?;
443 validate_payload_timestamp(
444 &self.inner.chain_spec,
445 version,
446 timestamp,
447 MessageValidationKind::GetPayload,
448 )?;
449
450 self.get_built_payload(payload_id).await?.try_into().map_err(|_| {
452 warn!(?version, "could not transform built payload");
453 EngineApiError::UnknownPayload
454 })
455 }
456
457 pub async fn get_payload_v1(
467 &self,
468 payload_id: PayloadId,
469 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV1> {
470 self.get_built_payload(payload_id).await?.try_into().map_err(|_| {
471 warn!(version = ?EngineApiMessageVersion::V1, "could not transform built payload");
472 EngineApiError::UnknownPayload
473 })
474 }
475
476 pub async fn get_payload_v1_metered(
478 &self,
479 payload_id: PayloadId,
480 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV1> {
481 let start = Instant::now();
482 let res = Self::get_payload_v1(self, payload_id).await;
483 self.inner.metrics.latency.get_payload_v1.record(start.elapsed());
484 res
485 }
486
487 pub async fn get_payload_v2(
495 &self,
496 payload_id: PayloadId,
497 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV2> {
498 self.get_payload_inner(payload_id, EngineApiMessageVersion::V2).await
499 }
500
501 pub async fn get_payload_v2_metered(
503 &self,
504 payload_id: PayloadId,
505 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV2> {
506 let start = Instant::now();
507 let res = Self::get_payload_v2(self, payload_id).await;
508 self.inner.metrics.latency.get_payload_v2.record(start.elapsed());
509 res
510 }
511
512 pub async fn get_payload_v3(
520 &self,
521 payload_id: PayloadId,
522 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV3> {
523 self.get_payload_inner(payload_id, EngineApiMessageVersion::V3).await
524 }
525
526 pub async fn get_payload_v3_metered(
528 &self,
529 payload_id: PayloadId,
530 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV3> {
531 let start = Instant::now();
532 let res = Self::get_payload_v3(self, payload_id).await;
533 self.inner.metrics.latency.get_payload_v3.record(start.elapsed());
534 res
535 }
536
537 pub async fn get_payload_v4(
545 &self,
546 payload_id: PayloadId,
547 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV4> {
548 self.get_payload_inner(payload_id, EngineApiMessageVersion::V4).await
549 }
550
551 pub async fn get_payload_v4_metered(
553 &self,
554 payload_id: PayloadId,
555 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV4> {
556 let start = Instant::now();
557 let res = Self::get_payload_v4(self, payload_id).await;
558 self.inner.metrics.latency.get_payload_v4.record(start.elapsed());
559 res
560 }
561
562 pub async fn get_payload_v5(
572 &self,
573 payload_id: PayloadId,
574 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV5> {
575 self.get_payload_inner(payload_id, EngineApiMessageVersion::V5).await
576 }
577
578 pub async fn get_payload_v5_metered(
580 &self,
581 payload_id: PayloadId,
582 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV5> {
583 let start = Instant::now();
584 let res = Self::get_payload_v5(self, payload_id).await;
585 self.inner.metrics.latency.get_payload_v5.record(start.elapsed());
586 res
587 }
588
589 pub async fn get_payload_v6(
595 &self,
596 payload_id: PayloadId,
597 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV6> {
598 self.get_payload_inner(payload_id, EngineApiMessageVersion::V6).await
599 }
600
601 pub async fn get_payload_v6_metered(
603 &self,
604 payload_id: PayloadId,
605 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV6> {
606 let start = Instant::now();
607 let res = Self::get_payload_v6(self, payload_id).await;
608 self.inner.metrics.latency.get_payload_v6.record(start.elapsed());
609 res
610 }
611
612 pub async fn get_payload_bodies_by_range_with<F, R>(
615 &self,
616 start: BlockNumber,
617 count: u64,
618 f: F,
619 ) -> EngineApiResult<Vec<Option<R>>>
620 where
621 F: Fn(Provider::Block) -> R + Send + 'static,
622 R: Send + 'static,
623 {
624 let (tx, rx) = oneshot::channel();
625 let inner = self.inner.clone();
626
627 self.inner.task_spawner.spawn_blocking_task(async move {
628 if count > MAX_PAYLOAD_BODIES_LIMIT {
629 tx.send(Err(EngineApiError::PayloadRequestTooLarge { len: count })).ok();
630 return;
631 }
632
633 if start == 0 || count == 0 {
634 tx.send(Err(EngineApiError::InvalidBodiesRange { start, count })).ok();
635 return;
636 }
637
638 let mut result = Vec::with_capacity(count as usize);
639
640 let mut end = start.saturating_add(count - 1);
642
643 if let Ok(best_block) = inner.provider.best_block_number()
646 && end > best_block {
647 end = best_block;
648 }
649
650 let earliest_block = inner.provider.earliest_block_number().unwrap_or(0);
652 for num in start..=end {
653 if num < earliest_block {
654 result.push(None);
655 continue;
656 }
657 let block_result = inner.provider.block(BlockHashOrNumber::Number(num));
658 match block_result {
659 Ok(block) => {
660 result.push(block.map(&f));
661 }
662 Err(err) => {
663 tx.send(Err(EngineApiError::Internal(Box::new(err)))).ok();
664 return;
665 }
666 };
667 }
668 tx.send(Ok(result)).ok();
669 });
670
671 rx.await.map_err(|err| EngineApiError::Internal(Box::new(err)))?
672 }
673
674 pub async fn get_payload_bodies_by_range_v1(
685 &self,
686 start: BlockNumber,
687 count: u64,
688 ) -> EngineApiResult<ExecutionPayloadBodiesV1> {
689 self.get_payload_bodies_by_range_with(start, count, |block| ExecutionPayloadBodyV1 {
690 transactions: block.body().encoded_2718_transactions(),
691 withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner),
692 })
693 .await
694 }
695
696 pub async fn get_payload_bodies_by_range_v1_metered(
698 &self,
699 start: BlockNumber,
700 count: u64,
701 ) -> EngineApiResult<ExecutionPayloadBodiesV1> {
702 let start_time = Instant::now();
703 let res = Self::get_payload_bodies_by_range_v1(self, start, count).await;
704 self.inner.metrics.latency.get_payload_bodies_by_range_v1.record(start_time.elapsed());
705 res
706 }
707
708 pub async fn get_payload_bodies_by_range_v2(
712 &self,
713 start: BlockNumber,
714 count: u64,
715 ) -> EngineApiResult<ExecutionPayloadBodiesV2> {
716 let mut payload_bodies = self
717 .get_payload_bodies_by_range_with(start, count, |block| {
718 let block_hash = block.header().hash_slow();
719 (
720 block_hash,
721 ExecutionPayloadBodyV2 {
722 transactions: block.body().encoded_2718_transactions(),
723 withdrawals: block
724 .body()
725 .withdrawals()
726 .cloned()
727 .map(Withdrawals::into_inner),
728 block_access_list: None,
729 },
730 )
731 })
732 .await?;
733
734 let block_hashes = payload_bodies
735 .iter()
736 .filter_map(|payload_body| payload_body.as_ref().map(|(block_hash, _)| *block_hash))
737 .collect::<Vec<_>>();
738 let block_access_lists = self.get_block_access_lists_by_hashes(block_hashes).await?;
739
740 for (payload_body, block_access_list) in
741 payload_bodies.iter_mut().filter_map(Option::as_mut).zip(block_access_lists)
742 {
743 payload_body.1.block_access_list = block_access_list;
744 }
745
746 Ok(payload_bodies
747 .into_iter()
748 .map(|payload_body| payload_body.map(|(_, payload_body)| payload_body))
749 .collect())
750 }
751
752 pub async fn get_payload_bodies_by_range_v2_metered(
754 &self,
755 start: BlockNumber,
756 count: u64,
757 ) -> EngineApiResult<ExecutionPayloadBodiesV2> {
758 let start_time = Instant::now();
759 let res = Self::get_payload_bodies_by_range_v2(self, start, count).await;
760 self.inner.metrics.latency.get_payload_bodies_by_range_v2.record(start_time.elapsed());
761 res
762 }
763
764 pub async fn get_payload_bodies_by_hash_with<F, R>(
766 &self,
767 hashes: Vec<BlockHash>,
768 f: F,
769 ) -> EngineApiResult<Vec<Option<R>>>
770 where
771 F: Fn(Provider::Block) -> R + Send + 'static,
772 R: Send + 'static,
773 {
774 let len = hashes.len() as u64;
775 if len > MAX_PAYLOAD_BODIES_LIMIT {
776 return Err(EngineApiError::PayloadRequestTooLarge { len });
777 }
778
779 let (tx, rx) = oneshot::channel();
780 let inner = self.inner.clone();
781
782 self.inner.task_spawner.spawn_blocking_task(async move {
783 let mut result = Vec::with_capacity(hashes.len());
784 for hash in hashes {
785 let block_result = inner.provider.block(BlockHashOrNumber::Hash(hash));
786 match block_result {
787 Ok(block) => {
788 result.push(block.map(&f));
789 }
790 Err(err) => {
791 let _ = tx.send(Err(EngineApiError::Internal(Box::new(err))));
792 return;
793 }
794 }
795 }
796 tx.send(Ok(result)).ok();
797 });
798
799 rx.await.map_err(|err| EngineApiError::Internal(Box::new(err)))?
800 }
801
802 async fn get_block_access_lists_by_hashes(
803 &self,
804 hashes: Vec<BlockHash>,
805 ) -> EngineApiResult<Vec<Option<Bytes>>> {
806 let len = hashes.len() as u64;
807 if len > MAX_PAYLOAD_BODIES_LIMIT {
808 return Err(EngineApiError::PayloadRequestTooLarge { len });
809 }
810
811 let (tx, rx) = oneshot::channel();
812 let bal_store = self.inner.provider.bal_store().clone();
813
814 self.inner.task_spawner.spawn_blocking_task(async move {
815 tx.send(
816 bal_store
817 .get_by_hashes(&hashes)
818 .map_err(|err| EngineApiError::Internal(Box::new(err))),
819 )
820 .ok();
821 });
822
823 rx.await.map_err(|err| EngineApiError::Internal(Box::new(err)))?
824 }
825
826 pub async fn get_payload_bodies_by_hash_v1(
828 &self,
829 hashes: Vec<BlockHash>,
830 ) -> EngineApiResult<ExecutionPayloadBodiesV1> {
831 self.get_payload_bodies_by_hash_with(hashes, |block| ExecutionPayloadBodyV1 {
832 transactions: block.body().encoded_2718_transactions(),
833 withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner),
834 })
835 .await
836 }
837
838 pub async fn get_payload_bodies_by_hash_v1_metered(
840 &self,
841 hashes: Vec<BlockHash>,
842 ) -> EngineApiResult<ExecutionPayloadBodiesV1> {
843 let start = Instant::now();
844 let res = Self::get_payload_bodies_by_hash_v1(self, hashes).await;
845 self.inner.metrics.latency.get_payload_bodies_by_hash_v1.record(start.elapsed());
846 res
847 }
848
849 pub async fn get_payload_bodies_by_hash_v2(
853 &self,
854 hashes: Vec<BlockHash>,
855 ) -> EngineApiResult<ExecutionPayloadBodiesV2> {
856 let payload_bodies =
857 self.get_payload_bodies_by_hash_with(hashes.clone(), |block| ExecutionPayloadBodyV2 {
858 transactions: block.body().encoded_2718_transactions(),
859 withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner),
860 block_access_list: None,
861 });
862 let block_access_lists = self.get_block_access_lists_by_hashes(hashes);
863 let (mut payload_bodies, block_access_lists) =
864 tokio::try_join!(payload_bodies, block_access_lists)?;
865
866 for (payload_body, block_access_list) in payload_bodies.iter_mut().zip(block_access_lists) {
867 if let Some(payload_body) = payload_body {
868 payload_body.block_access_list = block_access_list;
869 }
870 }
871
872 Ok(payload_bodies)
873 }
874
875 pub async fn get_payload_bodies_by_hash_v2_metered(
877 &self,
878 hashes: Vec<BlockHash>,
879 ) -> EngineApiResult<ExecutionPayloadBodiesV2> {
880 let start = Instant::now();
881 let res = Self::get_payload_bodies_by_hash_v2(self, hashes).await;
882 self.inner.metrics.latency.get_payload_bodies_by_hash_v2.record(start.elapsed());
883 res
884 }
885
886 async fn validate_and_execute_forkchoice(
900 &self,
901 version: EngineApiMessageVersion,
902 state: ForkchoiceState,
903 payload_attrs: Option<EngineT::PayloadAttributes>,
904 ) -> EngineApiResult<ForkchoiceUpdated> {
905 if let Some(ref attrs) = payload_attrs {
906 let attr_validation_res =
907 self.inner.validator.ensure_well_formed_attributes(version, attrs);
908
909 if let Err(err) = attr_validation_res {
919 let fcu_res = self.inner.beacon_consensus.fork_choice_updated(state, None).await?;
920 if fcu_res.is_invalid() || fcu_res.payload_status.is_syncing() {
921 return Ok(fcu_res)
922 }
923 return Err(err.into())
924 }
925 }
926
927 Ok(self.inner.beacon_consensus.fork_choice_updated(state, payload_attrs).await?)
928 }
929
930 pub fn capabilities(&self) -> &EngineCapabilities {
932 &self.inner.capabilities
933 }
934
935 fn has_blobs(&self, versioned_hashes: Vec<B256>) -> EngineApiResult<Vec<bool>> {
936 if versioned_hashes.len() > MAX_BLOB_LIMIT {
937 return Err(EngineApiError::BlobRequestTooLarge { len: versioned_hashes.len() })
938 }
939
940 self.inner
941 .tx_pool
942 .has_blobs_for_versioned_hashes(&versioned_hashes)
943 .map_err(|err| EngineApiError::Internal(Box::new(err)))
944 }
945
946 pub fn has_blobs_metered(&self, versioned_hashes: Vec<B256>) -> EngineApiResult<Vec<bool>> {
948 let start = Instant::now();
949 let res = Self::has_blobs(self, versioned_hashes);
950 self.inner.metrics.latency.has_blobs.record(start.elapsed());
951 res
952 }
953
954 fn get_blobs_v1(
955 &self,
956 versioned_hashes: Vec<B256>,
957 ) -> EngineApiResult<Vec<Option<BlobAndProofV1>>> {
958 let current_timestamp =
960 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs();
961 if self.inner.chain_spec.is_osaka_active_at_timestamp(current_timestamp) {
962 return Err(EngineApiError::EngineObjectValidationError(
963 reth_payload_primitives::EngineObjectValidationError::UnsupportedFork,
964 ));
965 }
966
967 if versioned_hashes.len() > MAX_BLOB_LIMIT {
968 return Err(EngineApiError::BlobRequestTooLarge { len: versioned_hashes.len() })
969 }
970
971 self.inner
972 .tx_pool
973 .get_blobs_for_versioned_hashes_v1(&versioned_hashes)
974 .map_err(|err| EngineApiError::Internal(Box::new(err)))
975 }
976
977 pub fn get_blobs_v1_metered(
979 &self,
980 versioned_hashes: Vec<B256>,
981 ) -> EngineApiResult<Vec<Option<BlobAndProofV1>>> {
982 let hashes_len = versioned_hashes.len();
983 let start = Instant::now();
984 let res = Self::get_blobs_v1(self, versioned_hashes);
985 self.inner.metrics.latency.get_blobs_v1.record(start.elapsed());
986
987 if let Ok(blobs) = &res {
988 let blobs_found = blobs.iter().flatten().count();
989 let blobs_missed = hashes_len - blobs_found;
990
991 self.inner.metrics.blob_metrics.blob_count.increment(blobs_found as u64);
992 self.inner.metrics.blob_metrics.blob_misses.increment(blobs_missed as u64);
993 }
994
995 res
996 }
997
998 fn get_blobs_v2(
999 &self,
1000 versioned_hashes: Vec<B256>,
1001 ) -> EngineApiResult<Option<Vec<BlobAndProofV2>>> {
1002 let current_timestamp =
1004 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs();
1005 if !self.inner.chain_spec.is_osaka_active_at_timestamp(current_timestamp) {
1006 return Err(EngineApiError::EngineObjectValidationError(
1007 reth_payload_primitives::EngineObjectValidationError::UnsupportedFork,
1008 ));
1009 }
1010
1011 if versioned_hashes.len() > MAX_BLOB_LIMIT {
1012 return Err(EngineApiError::BlobRequestTooLarge { len: versioned_hashes.len() })
1013 }
1014
1015 self.inner
1016 .tx_pool
1017 .get_blobs_for_versioned_hashes_v2(&versioned_hashes)
1018 .map_err(|err| EngineApiError::Internal(Box::new(err)))
1019 }
1020
1021 fn get_blobs_v3(
1022 &self,
1023 versioned_hashes: Vec<B256>,
1024 ) -> EngineApiResult<Option<Vec<Option<BlobAndProofV2>>>> {
1025 let current_timestamp =
1027 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs();
1028 if !self.inner.chain_spec.is_osaka_active_at_timestamp(current_timestamp) {
1029 return Err(EngineApiError::EngineObjectValidationError(
1030 reth_payload_primitives::EngineObjectValidationError::UnsupportedFork,
1031 ));
1032 }
1033
1034 if versioned_hashes.len() > MAX_BLOB_LIMIT {
1035 return Err(EngineApiError::BlobRequestTooLarge { len: versioned_hashes.len() })
1036 }
1037
1038 if (*self.inner.is_syncing)() {
1040 return Ok(None)
1041 }
1042
1043 self.inner
1044 .tx_pool
1045 .get_blobs_for_versioned_hashes_v3(&versioned_hashes)
1046 .map(Some)
1047 .map_err(|err| EngineApiError::Internal(Box::new(err)))
1048 }
1049
1050 fn get_blobs_v4(
1051 &self,
1052 versioned_hashes: Vec<B256>,
1053 indices_bitarray: B128,
1054 ) -> EngineApiResult<Option<Vec<Option<BlobCellsAndProofsV1>>>> {
1055 let current_timestamp =
1056 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs();
1057 if !self.inner.chain_spec.is_amsterdam_active_at_timestamp(current_timestamp) {
1058 return Err(EngineApiError::EngineObjectValidationError(
1059 reth_payload_primitives::EngineObjectValidationError::UnsupportedFork,
1060 ));
1061 }
1062
1063 if versioned_hashes.len() > MAX_BLOB_LIMIT {
1064 return Err(EngineApiError::BlobRequestTooLarge { len: versioned_hashes.len() })
1065 }
1066
1067 if (*self.inner.is_syncing)() {
1069 return Ok(None)
1070 }
1071
1072 self.inner
1073 .tx_pool
1074 .get_blobs_for_versioned_hashes_v4(&versioned_hashes, indices_bitarray)
1075 .map(Some)
1076 .map_err(|err| EngineApiError::Internal(Box::new(err)))
1077 }
1078
1079 pub fn get_blobs_v2_metered(
1081 &self,
1082 versioned_hashes: Vec<B256>,
1083 ) -> EngineApiResult<Option<Vec<BlobAndProofV2>>> {
1084 let hashes_len = versioned_hashes.len();
1085 let start = Instant::now();
1086 let res = Self::get_blobs_v2(self, versioned_hashes);
1087 self.inner.metrics.latency.get_blobs_v2.record(start.elapsed());
1088
1089 if let Ok(blobs) = &res {
1090 let blobs_found = blobs.iter().flatten().count();
1091
1092 self.inner
1093 .metrics
1094 .blob_metrics
1095 .get_blobs_requests_blobs_total
1096 .increment(hashes_len as u64);
1097 self.inner
1098 .metrics
1099 .blob_metrics
1100 .get_blobs_requests_blobs_in_blobpool_total
1101 .increment(blobs_found as u64);
1102
1103 if blobs_found == hashes_len {
1104 self.inner.metrics.blob_metrics.get_blobs_requests_success_total.increment(1);
1105 } else {
1106 self.inner.metrics.blob_metrics.get_blobs_requests_failure_total.increment(1);
1107 }
1108 } else {
1109 self.inner.metrics.blob_metrics.get_blobs_requests_failure_total.increment(1);
1110 }
1111
1112 res
1113 }
1114
1115 pub fn get_blobs_v3_metered(
1117 &self,
1118 versioned_hashes: Vec<B256>,
1119 ) -> EngineApiResult<Option<Vec<Option<BlobAndProofV2>>>> {
1120 let hashes_len = versioned_hashes.len();
1121 let start = Instant::now();
1122 let res = Self::get_blobs_v3(self, versioned_hashes);
1123 self.inner.metrics.latency.get_blobs_v3.record(start.elapsed());
1124
1125 if let Ok(Some(blobs)) = &res {
1126 let blobs_found = blobs.iter().flatten().count();
1127 let blobs_missed = hashes_len - blobs_found;
1128
1129 self.inner.metrics.blob_metrics.blob_count.increment(blobs_found as u64);
1130 self.inner.metrics.blob_metrics.blob_misses.increment(blobs_missed as u64);
1131 }
1132
1133 res
1134 }
1135
1136 pub fn get_blobs_v4_metered(
1138 &self,
1139 versioned_hashes: Vec<B256>,
1140 indices_bitarray: B128,
1141 ) -> EngineApiResult<Option<Vec<Option<BlobCellsAndProofsV1>>>> {
1142 let hashes_len = versioned_hashes.len();
1143 let start = Instant::now();
1144 let res = Self::get_blobs_v4(self, versioned_hashes, indices_bitarray);
1145 self.inner.metrics.latency.get_blobs_v4.record(start.elapsed());
1146
1147 if let Ok(Some(blobs)) = &res {
1148 let blobs_found = blobs.iter().flatten().count();
1149 let blobs_missed = hashes_len - blobs_found;
1150
1151 self.inner.metrics.blob_metrics.blob_count.increment(blobs_found as u64);
1152 self.inner.metrics.blob_metrics.blob_misses.increment(blobs_missed as u64);
1153 }
1154
1155 res
1156 }
1157}
1158
1159#[async_trait]
1161impl<Provider, EngineT, Pool, Validator, ChainSpec> EngineApiServer<EngineT>
1162 for EngineApi<Provider, EngineT, Pool, Validator, ChainSpec>
1163where
1164 Provider: HeaderProvider + BlockReader + StateProviderFactory + BalProvider + 'static,
1165 EngineT: EngineTypes<ExecutionData = ExecutionData>,
1166 Pool: TransactionPool + 'static,
1167 Validator: EngineApiValidator<EngineT>,
1168 ChainSpec: EthereumHardforks + Send + Sync + 'static,
1169{
1170 async fn new_payload_v1(&self, payload: ExecutionPayloadV1) -> RpcResult<PayloadStatus> {
1174 trace!(target: "rpc::engine", "Serving engine_newPayloadV1");
1175 let payload =
1176 ExecutionData { payload: payload.into(), sidecar: ExecutionPayloadSidecar::none() };
1177 Ok(self.new_payload_v1_metered(payload).await?)
1178 }
1179
1180 async fn new_payload_v2(&self, payload: ExecutionPayloadInputV2) -> RpcResult<PayloadStatus> {
1183 trace!(target: "rpc::engine", "Serving engine_newPayloadV2");
1184 let payload = ExecutionData {
1185 payload: payload.into_payload(),
1186 sidecar: ExecutionPayloadSidecar::none(),
1187 };
1188
1189 Ok(self.new_payload_v2_metered(payload).await?)
1190 }
1191
1192 async fn new_payload_v3(
1195 &self,
1196 payload: ExecutionPayloadV3,
1197 versioned_hashes: Vec<B256>,
1198 parent_beacon_block_root: B256,
1199 ) -> RpcResult<PayloadStatus> {
1200 trace!(target: "rpc::engine", "Serving engine_newPayloadV3");
1201 let payload = ExecutionData {
1202 payload: payload.into(),
1203 sidecar: ExecutionPayloadSidecar::v3(CancunPayloadFields {
1204 versioned_hashes,
1205 parent_beacon_block_root,
1206 }),
1207 };
1208
1209 Ok(self.new_payload_v3_metered(payload).await?)
1210 }
1211
1212 async fn new_payload_v4(
1215 &self,
1216 payload: ExecutionPayloadV3,
1217 versioned_hashes: Vec<B256>,
1218 parent_beacon_block_root: B256,
1219 requests: RequestsOrHash,
1220 ) -> RpcResult<PayloadStatus> {
1221 trace!(target: "rpc::engine", "Serving engine_newPayloadV4");
1222
1223 if requests.is_hash() && !self.inner.accept_execution_requests_hash {
1225 return Err(EngineApiError::UnexpectedRequestsHash.into());
1226 }
1227
1228 let payload = ExecutionData {
1229 payload: payload.into(),
1230 sidecar: ExecutionPayloadSidecar::v4(
1231 CancunPayloadFields { versioned_hashes, parent_beacon_block_root },
1232 PraguePayloadFields { requests },
1233 ),
1234 };
1235
1236 Ok(self.new_payload_v4_metered(payload).await?)
1237 }
1238
1239 async fn new_payload_v5(
1245 &self,
1246 payload: ExecutionPayloadV4,
1247 versioned_hashes: Vec<B256>,
1248 parent_beacon_block_root: B256,
1249 requests: RequestsOrHash,
1250 ) -> RpcResult<PayloadStatus> {
1251 trace!(target: "rpc::engine", "Serving engine_newPayloadV5");
1252 if requests.is_hash() && !self.inner.accept_execution_requests_hash {
1254 return Err(EngineApiError::UnexpectedRequestsHash.into());
1255 }
1256
1257 let payload = ExecutionData {
1258 payload: payload.into(),
1259 sidecar: ExecutionPayloadSidecar::v4(
1260 CancunPayloadFields { versioned_hashes, parent_beacon_block_root },
1261 PraguePayloadFields { requests },
1262 ),
1263 };
1264
1265 Ok(self.new_payload_v5_metered(payload).await?)
1266 }
1267
1268 async fn fork_choice_updated_v1(
1273 &self,
1274 fork_choice_state: ForkchoiceState,
1275 payload_attributes: Option<EngineT::PayloadAttributes>,
1276 ) -> RpcResult<ForkchoiceUpdated> {
1277 trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV1");
1278 Ok(self.fork_choice_updated_v1_metered(fork_choice_state, payload_attributes).await?)
1279 }
1280
1281 async fn fork_choice_updated_v2(
1284 &self,
1285 fork_choice_state: ForkchoiceState,
1286 payload_attributes: Option<EngineT::PayloadAttributes>,
1287 ) -> RpcResult<ForkchoiceUpdated> {
1288 trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV2");
1289 Ok(self.fork_choice_updated_v2_metered(fork_choice_state, payload_attributes).await?)
1290 }
1291
1292 async fn fork_choice_updated_v3(
1296 &self,
1297 fork_choice_state: ForkchoiceState,
1298 payload_attributes: Option<EngineT::PayloadAttributes>,
1299 ) -> RpcResult<ForkchoiceUpdated> {
1300 trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV3");
1301 Ok(self.fork_choice_updated_v3_metered(fork_choice_state, payload_attributes).await?)
1302 }
1303
1304 async fn fork_choice_updated_v4(
1308 &self,
1309 fork_choice_state: ForkchoiceState,
1310 payload_attributes: Option<EngineT::PayloadAttributes>,
1311 custody_columns: Option<B128>,
1312 ) -> RpcResult<ForkchoiceUpdated> {
1313 trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV4");
1314 Ok(self
1315 .fork_choice_updated_v4_metered(fork_choice_state, payload_attributes, custody_columns)
1316 .await?)
1317 }
1318
1319 async fn get_payload_v1(
1331 &self,
1332 payload_id: PayloadId,
1333 ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV1> {
1334 trace!(target: "rpc::engine", "Serving engine_getPayloadV1");
1335 Ok(self.get_payload_v1_metered(payload_id).await?)
1336 }
1337
1338 async fn get_payload_v2(
1348 &self,
1349 payload_id: PayloadId,
1350 ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV2> {
1351 debug!(target: "rpc::engine", id = %payload_id, "Serving engine_getPayloadV2");
1352 Ok(self.get_payload_v2_metered(payload_id).await?)
1353 }
1354
1355 async fn get_payload_v3(
1365 &self,
1366 payload_id: PayloadId,
1367 ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV3> {
1368 trace!(target: "rpc::engine", "Serving engine_getPayloadV3");
1369 Ok(self.get_payload_v3_metered(payload_id).await?)
1370 }
1371
1372 async fn get_payload_v4(
1382 &self,
1383 payload_id: PayloadId,
1384 ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV4> {
1385 trace!(target: "rpc::engine", "Serving engine_getPayloadV4");
1386 Ok(self.get_payload_v4_metered(payload_id).await?)
1387 }
1388
1389 async fn get_payload_v5(
1399 &self,
1400 payload_id: PayloadId,
1401 ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV5> {
1402 trace!(target: "rpc::engine", "Serving engine_getPayloadV5");
1403 Ok(self.get_payload_v5_metered(payload_id).await?)
1404 }
1405
1406 async fn get_payload_v6(
1412 &self,
1413 payload_id: PayloadId,
1414 ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV6> {
1415 trace!(target: "rpc::engine", "Serving engine_getPayloadV6");
1416 Ok(self.get_payload_v6_metered(payload_id).await?)
1417 }
1418
1419 async fn get_payload_bodies_by_hash_v1(
1422 &self,
1423 block_hashes: Vec<BlockHash>,
1424 ) -> RpcResult<ExecutionPayloadBodiesV1> {
1425 trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByHashV1");
1426 Ok(self.get_payload_bodies_by_hash_v1_metered(block_hashes).await?)
1427 }
1428
1429 async fn get_payload_bodies_by_hash_v2(
1435 &self,
1436 block_hashes: Vec<BlockHash>,
1437 ) -> RpcResult<ExecutionPayloadBodiesV2> {
1438 trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByHashV2");
1439 Ok(self.get_payload_bodies_by_hash_v2_metered(block_hashes).await?)
1440 }
1441
1442 async fn get_payload_bodies_by_range_v1(
1459 &self,
1460 start: U64,
1461 count: U64,
1462 ) -> RpcResult<ExecutionPayloadBodiesV1> {
1463 trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByRangeV1");
1464 Ok(self.get_payload_bodies_by_range_v1_metered(start.to(), count.to()).await?)
1465 }
1466
1467 async fn get_payload_bodies_by_range_v2(
1473 &self,
1474 start: U64,
1475 count: U64,
1476 ) -> RpcResult<ExecutionPayloadBodiesV2> {
1477 trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByRangeV2");
1478 Ok(self.get_payload_bodies_by_range_v2_metered(start.to(), count.to()).await?)
1479 }
1480
1481 async fn get_client_version_v1(
1485 &self,
1486 client: ClientVersionV1,
1487 ) -> RpcResult<Vec<ClientVersionV1>> {
1488 trace!(target: "rpc::engine", "Serving engine_getClientVersionV1");
1489 Ok(Self::get_client_version_v1(self, client)?)
1490 }
1491
1492 async fn exchange_capabilities(&self, capabilities: Vec<String>) -> RpcResult<Vec<String>> {
1495 trace!(target: "rpc::engine", "Serving engine_exchangeCapabilities");
1496
1497 let el_caps = self.capabilities();
1498 el_caps.log_capability_mismatches(&capabilities);
1499
1500 Ok(el_caps.list())
1501 }
1502
1503 async fn has_blobs(&self, versioned_hashes: Vec<B256>) -> RpcResult<Vec<bool>> {
1504 trace!(target: "rpc::engine", "Serving engine_hasBlobs");
1505 Ok(self.has_blobs_metered(versioned_hashes)?)
1506 }
1507
1508 async fn get_blobs_v1(
1509 &self,
1510 versioned_hashes: Vec<B256>,
1511 ) -> RpcResult<Vec<Option<BlobAndProofV1>>> {
1512 trace!(target: "rpc::engine", "Serving engine_getBlobsV1");
1513 Ok(self.get_blobs_v1_metered(versioned_hashes)?)
1514 }
1515
1516 async fn get_blobs_v2(
1517 &self,
1518 versioned_hashes: Vec<B256>,
1519 ) -> RpcResult<Option<Vec<BlobAndProofV2>>> {
1520 trace!(target: "rpc::engine", "Serving engine_getBlobsV2");
1521 Ok(self.get_blobs_v2_metered(versioned_hashes)?)
1522 }
1523
1524 async fn get_blobs_v3(
1525 &self,
1526 versioned_hashes: Vec<B256>,
1527 ) -> RpcResult<Option<Vec<Option<BlobAndProofV2>>>> {
1528 trace!(target: "rpc::engine", "Serving engine_getBlobsV3");
1529 Ok(self.get_blobs_v3_metered(versioned_hashes)?)
1530 }
1531
1532 async fn get_blobs_v4(
1533 &self,
1534 versioned_hashes: Vec<B256>,
1535 indices_bitarray: B128,
1536 ) -> RpcResult<Option<Vec<Option<BlobCellsAndProofsV1>>>> {
1537 trace!(target: "rpc::engine", "Serving engine_getBlobsV4");
1538 Ok(self.get_blobs_v4_metered(versioned_hashes, indices_bitarray)?)
1539 }
1540}
1541
1542impl<Provider, EngineT, Pool, Validator, ChainSpec> IntoEngineApiRpcModule
1543 for EngineApi<Provider, EngineT, Pool, Validator, ChainSpec>
1544where
1545 EngineT: EngineTypes,
1546 Self: EngineApiServer<EngineT>,
1547{
1548 fn into_rpc_module(self) -> RpcModule<()> {
1549 EngineApiServer::<EngineT>::into_rpc(self).remove_context()
1550 }
1551}
1552
1553impl<Provider, PayloadT, Pool, Validator, ChainSpec> std::fmt::Debug
1554 for EngineApi<Provider, PayloadT, Pool, Validator, ChainSpec>
1555where
1556 PayloadT: PayloadTypes,
1557{
1558 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1559 f.debug_struct("EngineApi").finish_non_exhaustive()
1560 }
1561}
1562
1563impl<Provider, PayloadT, Pool, Validator, ChainSpec> Clone
1564 for EngineApi<Provider, PayloadT, Pool, Validator, ChainSpec>
1565where
1566 PayloadT: PayloadTypes,
1567{
1568 fn clone(&self) -> Self {
1569 Self { inner: Arc::clone(&self.inner) }
1570 }
1571}
1572
1573struct EngineApiInner<Provider, PayloadT: PayloadTypes, Pool, Validator, ChainSpec> {
1575 provider: Provider,
1577 chain_spec: Arc<ChainSpec>,
1579 beacon_consensus: ConsensusEngineHandle<PayloadT>,
1581 payload_store: PayloadStore<PayloadT>,
1583 task_spawner: Runtime,
1585 metrics: EngineApiMetrics,
1587 client: ClientVersionV1,
1589 capabilities: EngineCapabilities,
1591 tx_pool: Pool,
1593 validator: Validator,
1595 accept_execution_requests_hash: bool,
1596 cell_custody: CellCustody,
1598 is_syncing: Arc<dyn Fn() -> bool + Send + Sync>,
1600}
1601
1602#[cfg(test)]
1603mod tests {
1604 use super::*;
1605 use alloy_eips::{eip7685::Requests, NumHash};
1606 use alloy_primitives::{Address, Bytes, B256};
1607 use alloy_rpc_types_engine::{
1608 ClientCode, ClientVersionV1, ExecutionPayloadV2, PayloadAttributes, PayloadStatusEnum,
1609 };
1610 use assert_matches::assert_matches;
1611 use reth_chainspec::{ChainSpec, ChainSpecBuilder, MAINNET};
1612 use reth_engine_primitives::{BeaconEngineMessage, OnForkChoiceUpdated};
1613 use reth_ethereum_engine_primitives::EthEngineTypes;
1614 use reth_ethereum_primitives::Block;
1615 use reth_network_api::{
1616 noop::NoopNetwork, EthProtocolInfo, NetworkError, NetworkInfo, NetworkStatus,
1617 };
1618 use reth_node_ethereum::EthereumEngineValidator;
1619 use reth_payload_builder::test_utils::spawn_test_payload_service;
1620 use reth_provider::{test_utils::MockEthProvider, BalStoreHandle, InMemoryBalStore, RawBal};
1621 use reth_tasks::Runtime;
1622 use reth_transaction_pool::noop::NoopTransactionPool;
1623 use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
1624
1625 fn setup_engine_api() -> (
1626 EngineApiTestHandle,
1627 EngineApi<
1628 Arc<MockEthProvider>,
1629 EthEngineTypes,
1630 NoopTransactionPool,
1631 EthereumEngineValidator,
1632 ChainSpec,
1633 >,
1634 ) {
1635 let client = ClientVersionV1 {
1636 code: ClientCode::RH,
1637 name: "Reth".to_string(),
1638 version: "v0.2.0-beta.5".to_string(),
1639 commit: "defa64b2".to_string(),
1640 };
1641
1642 let chain_spec: Arc<ChainSpec> = MAINNET.clone();
1643 let provider = Arc::new(MockEthProvider::default());
1644 let payload_store = spawn_test_payload_service();
1645 let (to_engine, engine_rx) = unbounded_channel();
1646 let task_executor = Runtime::test();
1647 let api = EngineApi::new(
1648 provider.clone(),
1649 chain_spec.clone(),
1650 ConsensusEngineHandle::new(to_engine),
1651 payload_store.into(),
1652 NoopTransactionPool::default(),
1653 task_executor,
1654 client,
1655 EngineCapabilities::default(),
1656 EthereumEngineValidator::new(chain_spec.clone()),
1657 false,
1658 NoopNetwork::default(),
1659 );
1660 let handle = EngineApiTestHandle { chain_spec, provider, from_api: engine_rx };
1661 (handle, api)
1662 }
1663
1664 #[tokio::test]
1665 async fn engine_client_version_v1() {
1666 let client = ClientVersionV1 {
1667 code: ClientCode::RH,
1668 name: "Reth".to_string(),
1669 version: "v0.2.0-beta.5".to_string(),
1670 commit: "defa64b2".to_string(),
1671 };
1672 let (_, api) = setup_engine_api();
1673 let res = api.get_client_version_v1(client.clone());
1674 assert_eq!(res.unwrap(), vec![client]);
1675 }
1676
1677 #[tokio::test]
1678 async fn has_blobs_returns_ordered_availability() {
1679 let (_, api) = setup_engine_api();
1680
1681 let res = api.has_blobs_metered(vec![B256::ZERO, B256::with_last_byte(1)]).unwrap();
1682 assert_eq!(res, vec![false, false]);
1683 }
1684
1685 #[tokio::test]
1686 async fn has_blobs_rejects_large_requests() {
1687 let (_, api) = setup_engine_api();
1688
1689 let res = api.has_blobs_metered(vec![B256::ZERO; MAX_BLOB_LIMIT + 1]);
1690 assert_matches!(
1691 res,
1692 Err(EngineApiError::BlobRequestTooLarge { len }) if len == MAX_BLOB_LIMIT + 1
1693 );
1694 }
1695
1696 #[tokio::test]
1697 async fn get_payload_bodies_by_hash_v2_returns_block_access_list_from_store() {
1698 let bal_store = BalStoreHandle::new(InMemoryBalStore::default());
1699 let mut provider = MockEthProvider::default();
1700 provider.bal_store = bal_store.clone();
1701 let provider = Arc::new(provider);
1702
1703 let client = ClientVersionV1 {
1704 code: ClientCode::RH,
1705 name: "Reth".to_string(),
1706 version: "v0.2.0-beta.5".to_string(),
1707 commit: "defa64b2".to_string(),
1708 };
1709 let chain_spec: Arc<ChainSpec> = MAINNET.clone();
1710 let payload_store = spawn_test_payload_service::<EthEngineTypes>();
1711 let (to_engine, _engine_rx) = unbounded_channel();
1712 let api = EngineApi::new(
1713 provider.clone(),
1714 chain_spec.clone(),
1715 ConsensusEngineHandle::new(to_engine),
1716 payload_store.into(),
1717 NoopTransactionPool::default(),
1718 Runtime::test(),
1719 client,
1720 EngineCapabilities::default(),
1721 EthereumEngineValidator::new(chain_spec),
1722 false,
1723 NoopNetwork::default(),
1724 );
1725
1726 let mut block = Block::default();
1727 block.header.number = 1;
1728 let block_hash = block.header.hash_slow();
1729 provider.add_block(block_hash, block);
1730
1731 let mut block_without_bal = Block::default();
1732 block_without_bal.header.number = 2;
1733 let block_without_bal_hash = block_without_bal.header.hash_slow();
1734 provider.add_block(block_without_bal_hash, block_without_bal);
1735
1736 let raw_bal = Bytes::from_static(&[alloy_rlp::EMPTY_LIST_CODE]);
1737 bal_store.insert(NumHash::new(1, block_hash), RawBal::new(raw_bal.clone())).unwrap();
1738
1739 let missing_hash = B256::with_last_byte(3);
1740 let response = api
1741 .get_payload_bodies_by_hash_v2(vec![block_hash, block_without_bal_hash, missing_hash])
1742 .await
1743 .unwrap();
1744
1745 assert_eq!(response.len(), 3);
1746 assert_eq!(response[0].as_ref().unwrap().block_access_list, Some(raw_bal));
1747 assert_eq!(response[1].as_ref().unwrap().block_access_list, None);
1748 assert!(response[2].is_none());
1749 }
1750
1751 #[tokio::test]
1752 async fn get_payload_bodies_by_range_v2_returns_block_access_lists_from_store() {
1753 let bal_store = BalStoreHandle::new(InMemoryBalStore::default());
1754 let mut provider = MockEthProvider::default();
1755 provider.bal_store = bal_store.clone();
1756 let provider = Arc::new(provider);
1757
1758 let client = ClientVersionV1 {
1759 code: ClientCode::RH,
1760 name: "Reth".to_string(),
1761 version: "v0.2.0-beta.5".to_string(),
1762 commit: "defa64b2".to_string(),
1763 };
1764 let chain_spec: Arc<ChainSpec> = MAINNET.clone();
1765 let payload_store = spawn_test_payload_service::<EthEngineTypes>();
1766 let (to_engine, _engine_rx) = unbounded_channel();
1767 let api = EngineApi::new(
1768 provider.clone(),
1769 chain_spec.clone(),
1770 ConsensusEngineHandle::new(to_engine),
1771 payload_store.into(),
1772 NoopTransactionPool::default(),
1773 Runtime::test(),
1774 client,
1775 EngineCapabilities::default(),
1776 EthereumEngineValidator::new(chain_spec),
1777 false,
1778 NoopNetwork::default(),
1779 );
1780
1781 let mut block = Block::default();
1782 block.header.number = 1;
1783 let block_hash = block.header.hash_slow();
1784 provider.add_block(block_hash, block);
1785
1786 let mut block_without_bal = Block::default();
1787 block_without_bal.header.number = 3;
1788 let block_without_bal_hash = block_without_bal.header.hash_slow();
1789 provider.add_block(block_without_bal_hash, block_without_bal);
1790
1791 let raw_bal = Bytes::from_static(&[alloy_rlp::EMPTY_LIST_CODE]);
1792 bal_store.insert(NumHash::new(1, block_hash), RawBal::new(raw_bal.clone())).unwrap();
1793
1794 let response = api.get_payload_bodies_by_range_v2(1, 3).await.unwrap();
1795
1796 assert_eq!(response.len(), 3);
1797 assert_eq!(response[0].as_ref().unwrap().block_access_list, Some(raw_bal));
1798 assert!(response[1].is_none());
1799 assert_eq!(response[2].as_ref().unwrap().block_access_list, None);
1800 }
1801
1802 struct EngineApiTestHandle {
1803 #[allow(dead_code)]
1804 chain_spec: Arc<ChainSpec>,
1805 provider: Arc<MockEthProvider>,
1806 from_api: UnboundedReceiver<BeaconEngineMessage<EthEngineTypes>>,
1807 }
1808
1809 #[tokio::test]
1810 async fn forwards_responses_to_consensus_engine() {
1811 let (mut handle, api) = setup_engine_api();
1812
1813 tokio::spawn(async move {
1814 let payload_v1 = ExecutionPayloadV1::from_block_slow(&Block::default());
1815 let execution_data = ExecutionData {
1816 payload: payload_v1.into(),
1817 sidecar: ExecutionPayloadSidecar::none(),
1818 };
1819
1820 api.new_payload_v1(execution_data).await.unwrap();
1821 });
1822 assert_matches!(handle.from_api.recv().await, Some(BeaconEngineMessage::NewPayload { .. }));
1823 }
1824
1825 #[tokio::test]
1826 async fn new_payload_v5_accepts_amsterdam_payloads() {
1827 let chain_spec = Arc::new(ChainSpecBuilder::mainnet().amsterdam_activated().build());
1828 let provider = Arc::new(MockEthProvider::default());
1829 let payload_store = spawn_test_payload_service::<EthEngineTypes>();
1830 let (to_engine, mut engine_rx) = unbounded_channel();
1831
1832 let api = EngineApi::new(
1833 provider,
1834 chain_spec.clone(),
1835 ConsensusEngineHandle::new(to_engine),
1836 payload_store.into(),
1837 NoopTransactionPool::default(),
1838 Runtime::test(),
1839 ClientVersionV1 {
1840 code: ClientCode::RH,
1841 name: "Reth".to_string(),
1842 version: "v0.0.0-test".to_string(),
1843 commit: "test".to_string(),
1844 },
1845 EngineCapabilities::default(),
1846 EthereumEngineValidator::new(chain_spec),
1847 false,
1848 NoopNetwork::default(),
1849 );
1850
1851 tokio::spawn(async move {
1852 let payload_v1 = ExecutionPayloadV1::from_block_slow(&Block::default());
1853 let payload = ExecutionPayloadV4 {
1854 payload_inner: ExecutionPayloadV3 {
1855 payload_inner: ExecutionPayloadV2 {
1856 payload_inner: payload_v1,
1857 withdrawals: Vec::new(),
1858 },
1859 blob_gas_used: 0,
1860 excess_blob_gas: 0,
1861 },
1862 block_access_list: Bytes::from_static(b"bal"),
1863 slot_number: 1,
1864 };
1865 let execution_data = ExecutionData {
1866 payload: payload.into(),
1867 sidecar: ExecutionPayloadSidecar::v4(
1868 CancunPayloadFields {
1869 versioned_hashes: Vec::new(),
1870 parent_beacon_block_root: B256::ZERO,
1871 },
1872 PraguePayloadFields { requests: RequestsOrHash::Requests(Requests::default()) },
1873 ),
1874 };
1875
1876 api.new_payload_v5(execution_data).await.unwrap();
1877 });
1878
1879 assert_matches!(engine_rx.recv().await, Some(BeaconEngineMessage::NewPayload { .. }));
1880 }
1881
1882 #[derive(Clone)]
1883 struct TestNetworkInfo {
1884 syncing: bool,
1885 }
1886
1887 impl NetworkInfo for TestNetworkInfo {
1888 fn local_addr(&self) -> std::net::SocketAddr {
1889 (std::net::Ipv4Addr::UNSPECIFIED, 0).into()
1890 }
1891
1892 async fn network_status(&self) -> Result<NetworkStatus, NetworkError> {
1893 #[allow(deprecated)]
1894 Ok(NetworkStatus {
1895 client_version: "test".to_string(),
1896 protocol_version: 5,
1897 eth_protocol_info: EthProtocolInfo {
1898 network: 1,
1899 difficulty: None,
1900 genesis: Default::default(),
1901 config: Default::default(),
1902 head: Default::default(),
1903 },
1904 capabilities: vec![],
1905 })
1906 }
1907
1908 fn chain_id(&self) -> u64 {
1909 1
1910 }
1911
1912 fn cell_custody(&self) -> &CellCustody {
1913 static CELL_CUSTODY: std::sync::OnceLock<CellCustody> = std::sync::OnceLock::new();
1914 CELL_CUSTODY.get_or_init(CellCustody::default)
1915 }
1916
1917 fn is_syncing(&self) -> bool {
1918 self.syncing
1919 }
1920
1921 fn is_initially_syncing(&self) -> bool {
1922 self.syncing
1923 }
1924 }
1925
1926 #[tokio::test]
1927 async fn get_blobs_v3_returns_null_when_syncing() {
1928 let chain_spec: Arc<ChainSpec> =
1929 Arc::new(ChainSpecBuilder::mainnet().osaka_activated().build());
1930 let provider = Arc::new(MockEthProvider::default());
1931 let payload_store = spawn_test_payload_service::<EthEngineTypes>();
1932 let (to_engine, _engine_rx) = unbounded_channel::<BeaconEngineMessage<EthEngineTypes>>();
1933
1934 let api = EngineApi::new(
1935 provider,
1936 chain_spec.clone(),
1937 ConsensusEngineHandle::new(to_engine),
1938 payload_store.into(),
1939 NoopTransactionPool::default(),
1940 Runtime::test(),
1941 ClientVersionV1 {
1942 code: ClientCode::RH,
1943 name: "Reth".to_string(),
1944 version: "v0.0.0-test".to_string(),
1945 commit: "test".to_string(),
1946 },
1947 EngineCapabilities::default(),
1948 EthereumEngineValidator::new(chain_spec),
1949 false,
1950 TestNetworkInfo { syncing: true },
1951 );
1952
1953 let res = api.get_blobs_v3_metered(vec![B256::ZERO]);
1954 assert_matches!(res, Ok(None));
1955 }
1956
1957 #[tokio::test]
1958 async fn get_blobs_v4_returns_null_when_syncing() {
1959 let chain_spec: Arc<ChainSpec> =
1960 Arc::new(ChainSpecBuilder::mainnet().amsterdam_activated().build());
1961 let provider = Arc::new(MockEthProvider::default());
1962 let payload_store = spawn_test_payload_service::<EthEngineTypes>();
1963 let (to_engine, _engine_rx) = unbounded_channel::<BeaconEngineMessage<EthEngineTypes>>();
1964
1965 let api = EngineApi::new(
1966 provider,
1967 chain_spec.clone(),
1968 ConsensusEngineHandle::new(to_engine),
1969 payload_store.into(),
1970 NoopTransactionPool::default(),
1971 Runtime::test(),
1972 ClientVersionV1 {
1973 code: ClientCode::RH,
1974 name: "Reth".to_string(),
1975 version: "v0.0.0-test".to_string(),
1976 commit: "test".to_string(),
1977 },
1978 EngineCapabilities::default(),
1979 EthereumEngineValidator::new(chain_spec),
1980 false,
1981 TestNetworkInfo { syncing: true },
1982 );
1983
1984 let res = api.get_blobs_v4_metered(vec![B256::ZERO], B128::from(1u128));
1985 assert_matches!(res, Ok(None));
1986 }
1987
1988 #[tokio::test]
1989 async fn fcu_v4_updates_shared_cell_custody_before_forkchoice_result() {
1990 let chain_spec: Arc<ChainSpec> =
1991 Arc::new(ChainSpecBuilder::mainnet().amsterdam_activated().build());
1992 let provider = Arc::new(MockEthProvider::default());
1993 let payload_store = spawn_test_payload_service::<EthEngineTypes>();
1994 let (to_engine, mut engine_rx) = unbounded_channel();
1995 let network = NoopNetwork::default();
1996 let cell_custody = network.cell_custody().clone();
1997
1998 let api = EngineApi::new(
1999 provider,
2000 chain_spec.clone(),
2001 ConsensusEngineHandle::new(to_engine),
2002 payload_store.into(),
2003 NoopTransactionPool::default(),
2004 Runtime::test(),
2005 ClientVersionV1 {
2006 code: ClientCode::RH,
2007 name: "Reth".to_string(),
2008 version: "v0.0.0-test".to_string(),
2009 commit: "test".to_string(),
2010 },
2011 EngineCapabilities::default(),
2012 EthereumEngineValidator::new(chain_spec),
2013 false,
2014 network,
2015 );
2016
2017 let state = ForkchoiceState {
2018 head_block_hash: B256::from([0x33; 32]),
2019 safe_block_hash: B256::ZERO,
2020 finalized_block_hash: B256::ZERO,
2021 };
2022 let custody_columns = B128::from(0b1010u128);
2023
2024 let api_task = tokio::spawn(async move {
2025 api.fork_choice_updated_v4(state, None, Some(custody_columns)).await
2026 });
2027
2028 let request = tokio::time::timeout(std::time::Duration::from_secs(1), engine_rx.recv())
2029 .await
2030 .expect("timed out waiting for forkchoiceUpdated request")
2031 .expect("expected forkchoiceUpdated request");
2032 let response_tx = match request {
2033 BeaconEngineMessage::ForkchoiceUpdated { payload_attrs, tx, .. } => {
2034 assert!(payload_attrs.is_none());
2035 tx
2036 }
2037 other => panic!("unexpected engine message: {other:?}"),
2038 };
2039 assert_eq!(cell_custody.get(), custody_columns);
2040
2041 response_tx
2042 .send(Ok(OnForkChoiceUpdated::valid(PayloadStatus::from_status(
2043 PayloadStatusEnum::Valid,
2044 ))))
2045 .expect("send valid response");
2046
2047 api_task
2048 .await
2049 .expect("api task should not panic")
2050 .expect("forkchoiceUpdatedV4 should succeed");
2051 assert_eq!(cell_custody.get(), custody_columns);
2052 }
2053
2054 #[tokio::test]
2055 async fn fcu_v4_updates_shared_cell_custody_when_payload_attrs_invalid() {
2056 let chain_spec: Arc<ChainSpec> =
2057 Arc::new(ChainSpecBuilder::mainnet().amsterdam_activated().build());
2058 let provider = Arc::new(MockEthProvider::default());
2059 let payload_store = spawn_test_payload_service::<EthEngineTypes>();
2060 let (to_engine, mut engine_rx) = unbounded_channel();
2061 let network = NoopNetwork::default();
2062 let cell_custody = network.cell_custody().clone();
2063
2064 let api = EngineApi::new(
2065 provider,
2066 chain_spec.clone(),
2067 ConsensusEngineHandle::new(to_engine),
2068 payload_store.into(),
2069 NoopTransactionPool::default(),
2070 Runtime::test(),
2071 ClientVersionV1 {
2072 code: ClientCode::RH,
2073 name: "Reth".to_string(),
2074 version: "v0.0.0-test".to_string(),
2075 commit: "test".to_string(),
2076 },
2077 EngineCapabilities::default(),
2078 EthereumEngineValidator::new(chain_spec),
2079 false,
2080 network,
2081 );
2082
2083 let state = ForkchoiceState {
2084 head_block_hash: B256::from([0x44; 32]),
2085 safe_block_hash: B256::ZERO,
2086 finalized_block_hash: B256::ZERO,
2087 };
2088 let payload_attributes = PayloadAttributes {
2089 timestamp: 1,
2090 prev_randao: B256::ZERO,
2091 suggested_fee_recipient: Address::ZERO,
2092 withdrawals: Some(vec![]),
2093 parent_beacon_block_root: None,
2094 slot_number: None,
2095 target_gas_limit: None,
2096 };
2097 let custody_columns = B128::from(0b1010u128);
2098
2099 let api_task = tokio::spawn(async move {
2100 api.fork_choice_updated_v4(state, Some(payload_attributes), Some(custody_columns)).await
2101 });
2102
2103 let request = tokio::time::timeout(std::time::Duration::from_secs(1), engine_rx.recv())
2104 .await
2105 .expect("timed out waiting for forkchoiceUpdated request")
2106 .expect("expected forkchoiceUpdated request");
2107 let response_tx = match request {
2108 BeaconEngineMessage::ForkchoiceUpdated { payload_attrs, tx, .. } => {
2109 assert!(
2110 payload_attrs.is_none(),
2111 "when attrs are invalid, API should first evaluate forkchoice without attrs"
2112 );
2113 tx
2114 }
2115 other => panic!("unexpected engine message: {other:?}"),
2116 };
2117 assert_eq!(cell_custody.get(), custody_columns);
2118
2119 response_tx
2120 .send(Ok(OnForkChoiceUpdated::valid(PayloadStatus::from_status(
2121 PayloadStatusEnum::Valid,
2122 ))))
2123 .expect("send valid response");
2124
2125 let response = api_task.await.expect("api task should not panic");
2126 assert_matches!(
2127 response,
2128 Err(EngineApiError::EngineObjectValidationError(
2129 reth_payload_primitives::EngineObjectValidationError::PayloadAttributes(_)
2130 ))
2131 );
2132 }
2133
2134 #[tokio::test]
2135 async fn fcu_v3_syncing_precedes_invalid_payload_attributes_validation() {
2136 let (mut handle, api) = setup_engine_api();
2137
2138 let state = ForkchoiceState {
2139 head_block_hash: B256::from([0x11; 32]),
2140 safe_block_hash: B256::ZERO,
2141 finalized_block_hash: B256::ZERO,
2142 };
2143 let payload_attributes = PayloadAttributes {
2144 timestamp: 1,
2145 prev_randao: B256::ZERO,
2146 suggested_fee_recipient: Address::ZERO,
2147 withdrawals: Some(vec![]),
2148 parent_beacon_block_root: None,
2150 slot_number: None,
2151 target_gas_limit: None,
2152 };
2153
2154 let api_task = tokio::spawn(async move {
2155 api.fork_choice_updated_v3(state, Some(payload_attributes)).await
2156 });
2157
2158 let request =
2159 tokio::time::timeout(std::time::Duration::from_secs(1), handle.from_api.recv())
2160 .await
2161 .expect("timed out waiting for forkchoiceUpdated request")
2162 .expect("expected forkchoiceUpdated request");
2163 let response_tx = match request {
2164 BeaconEngineMessage::ForkchoiceUpdated { payload_attrs, tx, .. } => {
2165 assert!(
2166 payload_attrs.is_none(),
2167 "FCU for syncing state should be evaluated before payload attributes"
2168 );
2169 tx
2170 }
2171 other => panic!("unexpected engine message: {other:?}"),
2172 };
2173
2174 response_tx.send(Ok(OnForkChoiceUpdated::syncing())).expect("send syncing response");
2175
2176 let response = api_task
2177 .await
2178 .expect("api task should not panic")
2179 .expect("forkchoiceUpdatedV3 should return a syncing response");
2180 assert!(response.payload_status.is_syncing());
2181 assert!(response.payload_id.is_none());
2182 }
2183
2184 #[tokio::test]
2185 async fn fcu_v3_valid_forkchoice_missing_beacon_root_returns_invalid_attributes() {
2186 let (mut handle, api) = setup_engine_api();
2187
2188 let state = ForkchoiceState {
2189 head_block_hash: B256::from([0x22; 32]),
2190 safe_block_hash: B256::ZERO,
2191 finalized_block_hash: B256::ZERO,
2192 };
2193 let payload_attributes = PayloadAttributes {
2194 timestamp: 1,
2195 prev_randao: B256::ZERO,
2196 suggested_fee_recipient: Address::ZERO,
2197 withdrawals: Some(vec![]),
2198 parent_beacon_block_root: None,
2199 slot_number: None,
2200 target_gas_limit: None,
2201 };
2202
2203 let api_task = tokio::spawn(async move {
2204 api.fork_choice_updated_v3(state, Some(payload_attributes)).await
2205 });
2206
2207 let request =
2208 tokio::time::timeout(std::time::Duration::from_secs(1), handle.from_api.recv())
2209 .await
2210 .expect("timed out waiting for forkchoiceUpdated request")
2211 .expect("expected forkchoiceUpdated request");
2212
2213 let response_tx = match request {
2214 BeaconEngineMessage::ForkchoiceUpdated { payload_attrs, tx, .. } => {
2215 assert!(
2216 payload_attrs.is_none(),
2217 "when attrs are invalid, API should first evaluate forkchoice without attrs"
2218 );
2219 tx
2220 }
2221 other => panic!("unexpected engine message: {other:?}"),
2222 };
2223
2224 response_tx
2225 .send(Ok(OnForkChoiceUpdated::valid(PayloadStatus::from_status(
2226 PayloadStatusEnum::Valid,
2227 ))))
2228 .expect("send valid response");
2229
2230 let response = api_task.await.expect("api task should not panic");
2231 assert_matches!(
2232 response,
2233 Err(EngineApiError::EngineObjectValidationError(
2234 reth_payload_primitives::EngineObjectValidationError::PayloadAttributes(_)
2235 ))
2236 );
2237
2238 match tokio::time::timeout(std::time::Duration::from_millis(100), handle.from_api.recv())
2239 .await
2240 {
2241 Err(_) | Ok(None) => {}
2242 Ok(Some(BeaconEngineMessage::ForkchoiceUpdated { .. })) => {
2243 panic!("no second forkchoiceUpdated call should be sent when attrs are invalid")
2244 }
2245 Ok(Some(other)) => panic!("unexpected engine message: {other:?}"),
2246 }
2247 }
2248
2249 mod get_payload_bodies {
2251 use super::*;
2252 use alloy_rpc_types_engine::ExecutionPayloadBodyV1;
2253 use reth_testing_utils::generators::{self, random_block_range, BlockRangeParams};
2254
2255 #[tokio::test]
2256 async fn invalid_params() {
2257 let (_, api) = setup_engine_api();
2258
2259 let by_range_tests = [
2260 (0, 0),
2262 (0, 1),
2263 (1, 0),
2264 ];
2265
2266 for (start, count) in by_range_tests {
2268 let res = api.get_payload_bodies_by_range_v1(start, count).await;
2269 assert_matches!(res, Err(EngineApiError::InvalidBodiesRange { .. }));
2270 }
2271 }
2272
2273 #[tokio::test]
2274 async fn request_too_large() {
2275 let (_, api) = setup_engine_api();
2276
2277 let request_count = MAX_PAYLOAD_BODIES_LIMIT + 1;
2278 let res = api.get_payload_bodies_by_range_v1(0, request_count).await;
2279 assert_matches!(res, Err(EngineApiError::PayloadRequestTooLarge { .. }));
2280 }
2281
2282 #[tokio::test]
2283 async fn returns_payload_bodies() {
2284 let mut rng = generators::rng();
2285 let (handle, api) = setup_engine_api();
2286
2287 let (start, count) = (1, 10);
2288 let blocks = random_block_range(
2289 &mut rng,
2290 start..=start + count - 1,
2291 BlockRangeParams { tx_count: 0..2, ..Default::default() },
2292 );
2293 handle
2294 .provider
2295 .extend_blocks(blocks.iter().cloned().map(|b| (b.hash(), b.into_block())));
2296
2297 let expected = blocks
2298 .iter()
2299 .cloned()
2300 .map(|b| Some(ExecutionPayloadBodyV1::from_block(b.into_block())))
2301 .collect::<Vec<_>>();
2302
2303 let res = api.get_payload_bodies_by_range_v1(start, count).await.unwrap();
2304 assert_eq!(res, expected);
2305 }
2306
2307 #[tokio::test]
2308 async fn returns_payload_bodies_with_gaps() {
2309 let mut rng = generators::rng();
2310 let (handle, api) = setup_engine_api();
2311
2312 let (start, count) = (1, 100);
2313 let blocks = random_block_range(
2314 &mut rng,
2315 start..=start + count - 1,
2316 BlockRangeParams { tx_count: 0..2, ..Default::default() },
2317 );
2318
2319 let first_missing_range = 26..=50;
2321 let second_missing_range = 76..=100;
2322 handle.provider.extend_blocks(
2323 blocks
2324 .iter()
2325 .filter(|b| {
2326 !first_missing_range.contains(&b.number) &&
2327 !second_missing_range.contains(&b.number)
2328 })
2329 .map(|b| (b.hash(), b.clone().into_block())),
2330 );
2331
2332 let expected = blocks
2333 .iter()
2334 .filter(|b| !second_missing_range.contains(&b.number))
2337 .cloned()
2338 .map(|b| {
2339 if first_missing_range.contains(&b.number) {
2340 None
2341 } else {
2342 Some(ExecutionPayloadBodyV1::from_block(b.into_block()))
2343 }
2344 })
2345 .collect::<Vec<_>>();
2346
2347 let res = api.get_payload_bodies_by_range_v1(start, count).await.unwrap();
2348 assert_eq!(res, expected);
2349
2350 let expected = blocks
2351 .iter()
2352 .cloned()
2353 .map(|b| {
2356 if first_missing_range.contains(&b.number) ||
2357 second_missing_range.contains(&b.number)
2358 {
2359 None
2360 } else {
2361 Some(ExecutionPayloadBodyV1::from_block(b.into_block()))
2362 }
2363 })
2364 .collect::<Vec<_>>();
2365
2366 let hashes = blocks.iter().map(|b| b.hash()).collect();
2367 let res = api.get_payload_bodies_by_hash_v1(hashes).await.unwrap();
2368 assert_eq!(res, expected);
2369 }
2370 }
2371}