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 eip7594::BlobCellMask,
9 eip7685::RequestsOrHash,
10};
11use alloy_primitives::{BlockHash, BlockNumber, Bytes, Sealable, B128, B256, U64};
12use alloy_rpc_types_engine::{
13 BogotaPayloadFields, CancunPayloadFields, ClientVersionV1, ExecutionData,
14 ExecutionPayloadBodiesV1, ExecutionPayloadBodiesV2, ExecutionPayloadBodyV1,
15 ExecutionPayloadBodyV2, ExecutionPayloadInputV2, ExecutionPayloadSidecar, ExecutionPayloadV1,
16 ExecutionPayloadV3, ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated,
17 ForkchoiceUpdatedResponseV2, PayloadId, PayloadStatus, PayloadStatusV2, PraguePayloadFields,
18 MAX_BYTES_PER_INCLUSION_LIST,
19};
20use jsonrpsee_core::{server::RpcModule, RpcResult};
21use reth_chainspec::EthereumHardforks;
22use reth_engine_primitives::{ConsensusEngineHandle, EngineApiValidator, EngineTypes};
23use reth_network_api::{CellCustody, NetworkInfo};
24use reth_payload_builder::PayloadStore;
25use reth_payload_primitives::{
26 validate_payload_timestamp, EngineApiMessageVersion, MessageValidationKind,
27 PayloadOrAttributes, PayloadTypes,
28};
29use reth_primitives_traits::{AlloyBlockHeader, Block, BlockBody};
30use reth_rpc_api::{EngineApiServer, IntoEngineApiRpcModule};
31use reth_storage_api::{BalProvider, BlockReader, StateProviderFactory};
32use reth_tasks::Runtime;
33use reth_transaction_pool::{BestTransactions, TransactionPool};
34use std::{
35 sync::Arc,
36 time::{Instant, SystemTime},
37};
38use tokio::sync::oneshot;
39use tracing::{debug, trace, warn};
40
41pub type EngineApiSender<Ok> = oneshot::Sender<EngineApiResult<Ok>>;
43
44const MAX_PAYLOAD_BODIES_LIMIT: u64 = 1024;
46
47const MAX_BLOB_LIMIT: usize = 128;
49
50pub struct EngineApi<Provider, PayloadT: PayloadTypes, Pool, Validator, ChainSpec> {
66 inner: Arc<EngineApiInner<Provider, PayloadT, Pool, Validator, ChainSpec>>,
67}
68
69impl<Provider, PayloadT: PayloadTypes, Pool, Validator, ChainSpec>
70 EngineApi<Provider, PayloadT, Pool, Validator, ChainSpec>
71{
72 pub fn chain_spec(&self) -> &Arc<ChainSpec> {
74 &self.inner.chain_spec
75 }
76
77 pub fn client_version(&self) -> &ClientVersionV1 {
79 &self.inner.client
80 }
81}
82
83impl<Provider, PayloadT, Pool, Validator, ChainSpec>
84 EngineApi<Provider, PayloadT, Pool, Validator, ChainSpec>
85where
86 Provider: BlockReader + StateProviderFactory + BalProvider + 'static,
87 PayloadT: PayloadTypes,
88 Pool: TransactionPool + 'static,
89 Validator: EngineApiValidator<PayloadT>,
90 ChainSpec: EthereumHardforks + Send + Sync + 'static,
91{
92 #[expect(clippy::too_many_arguments)]
94 pub fn new(
95 provider: Provider,
96 chain_spec: Arc<ChainSpec>,
97 beacon_consensus: ConsensusEngineHandle<PayloadT>,
98 payload_store: PayloadStore<PayloadT>,
99 tx_pool: Pool,
100 task_spawner: Runtime,
101 client: ClientVersionV1,
102 capabilities: EngineCapabilities,
103 validator: Validator,
104 accept_execution_requests_hash: bool,
105 network: impl NetworkInfo + 'static,
106 ) -> Self {
107 let cell_custody = network.cell_custody().clone();
108 let is_syncing = Arc::new(move || network.is_syncing());
109 let inner = Arc::new(EngineApiInner {
110 provider,
111 chain_spec,
112 beacon_consensus,
113 payload_store,
114 task_spawner,
115 metrics: EngineApiMetrics::default(),
116 client,
117 capabilities,
118 tx_pool,
119 validator,
120 accept_execution_requests_hash,
121 cell_custody,
122 is_syncing,
123 });
124 Self { inner }
125 }
126
127 pub fn get_client_version_v1(
129 &self,
130 _client: ClientVersionV1,
131 ) -> EngineApiResult<Vec<ClientVersionV1>> {
132 Ok(vec![self.inner.client.clone()])
133 }
134
135 async fn get_payload_timestamp(&self, payload_id: PayloadId) -> EngineApiResult<u64> {
137 Ok(self
138 .inner
139 .payload_store
140 .payload_timestamp(payload_id)
141 .await
142 .ok_or(EngineApiError::UnknownPayload)??)
143 }
144
145 pub async fn new_payload_v1(
148 &self,
149 payload: PayloadT::ExecutionData,
150 ) -> EngineApiResult<PayloadStatus> {
151 let payload_or_attrs = PayloadOrAttributes::<
152 '_,
153 PayloadT::ExecutionData,
154 PayloadT::PayloadAttributes,
155 >::from_execution_payload(&payload);
156
157 self.inner
158 .validator
159 .validate_version_specific_fields(EngineApiMessageVersion::V1, payload_or_attrs)?;
160
161 Ok(self.inner.beacon_consensus.new_payload(payload).await?)
162 }
163
164 pub async fn new_payload_v1_metered(
166 &self,
167 payload: PayloadT::ExecutionData,
168 ) -> EngineApiResult<PayloadStatus> {
169 let start = Instant::now();
170 let res = Self::new_payload_v1(self, payload).await;
171 let elapsed = start.elapsed();
172 self.inner.metrics.latency.new_payload_v1.record(elapsed);
173 res
174 }
175
176 pub async fn new_payload_v2(
178 &self,
179 payload: PayloadT::ExecutionData,
180 ) -> EngineApiResult<PayloadStatus> {
181 let payload_or_attrs = PayloadOrAttributes::<
182 '_,
183 PayloadT::ExecutionData,
184 PayloadT::PayloadAttributes,
185 >::from_execution_payload(&payload);
186 self.inner
187 .validator
188 .validate_version_specific_fields(EngineApiMessageVersion::V2, payload_or_attrs)?;
189 Ok(self.inner.beacon_consensus.new_payload(payload).await?)
190 }
191
192 pub async fn new_payload_v2_metered(
194 &self,
195 payload: PayloadT::ExecutionData,
196 ) -> EngineApiResult<PayloadStatus> {
197 let start = Instant::now();
198 let res = Self::new_payload_v2(self, payload).await;
199 let elapsed = start.elapsed();
200 self.inner.metrics.latency.new_payload_v2.record(elapsed);
201 res
202 }
203
204 pub async fn new_payload_v3(
206 &self,
207 payload: PayloadT::ExecutionData,
208 ) -> EngineApiResult<PayloadStatus> {
209 let payload_or_attrs = PayloadOrAttributes::<
210 '_,
211 PayloadT::ExecutionData,
212 PayloadT::PayloadAttributes,
213 >::from_execution_payload(&payload);
214 self.inner
215 .validator
216 .validate_version_specific_fields(EngineApiMessageVersion::V3, payload_or_attrs)?;
217
218 Ok(self.inner.beacon_consensus.new_payload(payload).await?)
219 }
220
221 pub async fn new_payload_v3_metered(
223 &self,
224 payload: PayloadT::ExecutionData,
225 ) -> RpcResult<PayloadStatus> {
226 let start = Instant::now();
227
228 let res = Self::new_payload_v3(self, payload).await;
229 let elapsed = start.elapsed();
230 self.inner.metrics.latency.new_payload_v3.record(elapsed);
231 Ok(res?)
232 }
233
234 pub async fn new_payload_v4(
236 &self,
237 payload: PayloadT::ExecutionData,
238 ) -> EngineApiResult<PayloadStatus> {
239 let payload_or_attrs = PayloadOrAttributes::<
240 '_,
241 PayloadT::ExecutionData,
242 PayloadT::PayloadAttributes,
243 >::from_execution_payload(&payload);
244 self.inner
245 .validator
246 .validate_version_specific_fields(EngineApiMessageVersion::V4, payload_or_attrs)?;
247
248 Ok(self.inner.beacon_consensus.new_payload(payload).await?)
249 }
250
251 pub async fn new_payload_v4_metered(
253 &self,
254 payload: PayloadT::ExecutionData,
255 ) -> RpcResult<PayloadStatus> {
256 let start = Instant::now();
257 let res = Self::new_payload_v4(self, payload).await;
258
259 let elapsed = start.elapsed();
260 self.inner.metrics.latency.new_payload_v4.record(elapsed);
261 Ok(res?)
262 }
263
264 pub async fn new_payload_v5(
270 &self,
271 payload: PayloadT::ExecutionData,
272 ) -> EngineApiResult<PayloadStatus> {
273 let payload_or_attrs = PayloadOrAttributes::<
274 '_,
275 PayloadT::ExecutionData,
276 PayloadT::PayloadAttributes,
277 >::from_execution_payload(&payload);
278 self.inner
279 .validator
280 .validate_version_specific_fields(EngineApiMessageVersion::V5, payload_or_attrs)?;
281 Ok(self.inner.beacon_consensus.new_payload(payload).await?)
282 }
283
284 pub async fn new_payload_v5_metered(
286 &self,
287 payload: PayloadT::ExecutionData,
288 ) -> RpcResult<PayloadStatus> {
289 let start = Instant::now();
290 let res = Self::new_payload_v5(self, payload).await;
291 let elapsed = start.elapsed();
292 self.inner.metrics.latency.new_payload_v5.record(elapsed);
293 Ok(res?)
294 }
295
296 pub async fn new_payload_v6(
300 &self,
301 payload: PayloadT::ExecutionData,
302 ) -> EngineApiResult<PayloadStatusV2> {
303 let payload_or_attrs = PayloadOrAttributes::<
304 '_,
305 PayloadT::ExecutionData,
306 PayloadT::PayloadAttributes,
307 >::from_execution_payload(&payload);
308 self.inner
309 .validator
310 .validate_version_specific_fields(EngineApiMessageVersion::V6, payload_or_attrs)?;
311
312 Ok(self.inner.beacon_consensus.new_payload(payload).await?.into())
313 }
314
315 pub async fn new_payload_v6_metered(
317 &self,
318 payload: PayloadT::ExecutionData,
319 ) -> EngineApiResult<PayloadStatusV2> {
320 let start = Instant::now();
321 let result = Self::new_payload_v6(self, payload).await;
322 self.inner.metrics.latency.new_payload_v6.record(start.elapsed());
323 result
324 }
325
326 pub fn accept_execution_requests_hash(&self) -> bool {
328 self.inner.accept_execution_requests_hash
329 }
330}
331
332impl<Provider, EngineT, Pool, Validator, ChainSpec>
333 EngineApi<Provider, EngineT, Pool, Validator, ChainSpec>
334where
335 Provider: BlockReader + StateProviderFactory + BalProvider + 'static,
336 EngineT: EngineTypes,
337 Pool: TransactionPool + 'static,
338 Validator: EngineApiValidator<EngineT>,
339 ChainSpec: EthereumHardforks + Send + Sync + 'static,
340{
341 pub async fn fork_choice_updated_v1(
348 &self,
349 state: ForkchoiceState,
350 payload_attrs: Option<EngineT::PayloadAttributes>,
351 ) -> EngineApiResult<ForkchoiceUpdated> {
352 self.validate_and_execute_forkchoice(EngineApiMessageVersion::V1, state, payload_attrs)
353 .await
354 }
355
356 pub async fn fork_choice_updated_v1_metered(
358 &self,
359 state: ForkchoiceState,
360 payload_attrs: Option<EngineT::PayloadAttributes>,
361 ) -> EngineApiResult<ForkchoiceUpdated> {
362 let start = Instant::now();
363 let res = Self::fork_choice_updated_v1(self, state, payload_attrs).await;
364 self.inner.metrics.latency.fork_choice_updated_v1.record(start.elapsed());
365 res
366 }
367
368 pub async fn fork_choice_updated_v2(
373 &self,
374 state: ForkchoiceState,
375 payload_attrs: Option<EngineT::PayloadAttributes>,
376 ) -> EngineApiResult<ForkchoiceUpdated> {
377 self.validate_and_execute_forkchoice(EngineApiMessageVersion::V2, state, payload_attrs)
378 .await
379 }
380
381 pub async fn fork_choice_updated_v2_metered(
383 &self,
384 state: ForkchoiceState,
385 payload_attrs: Option<EngineT::PayloadAttributes>,
386 ) -> EngineApiResult<ForkchoiceUpdated> {
387 let start = Instant::now();
388 let res = Self::fork_choice_updated_v2(self, state, payload_attrs).await;
389 self.inner.metrics.latency.fork_choice_updated_v2.record(start.elapsed());
390 res
391 }
392
393 pub async fn fork_choice_updated_v3(
398 &self,
399 state: ForkchoiceState,
400 payload_attrs: Option<EngineT::PayloadAttributes>,
401 ) -> EngineApiResult<ForkchoiceUpdated> {
402 self.validate_and_execute_forkchoice(EngineApiMessageVersion::V3, state, payload_attrs)
403 .await
404 }
405
406 pub async fn fork_choice_updated_v3_metered(
408 &self,
409 state: ForkchoiceState,
410 payload_attrs: Option<EngineT::PayloadAttributes>,
411 ) -> EngineApiResult<ForkchoiceUpdated> {
412 let start = Instant::now();
413 let res = Self::fork_choice_updated_v3(self, state, payload_attrs).await;
414 self.inner.metrics.latency.fork_choice_updated_v3.record(start.elapsed());
415 res
416 }
417
418 pub async fn fork_choice_updated_v4(
423 &self,
424 state: ForkchoiceState,
425 payload_attrs: Option<EngineT::PayloadAttributes>,
426 custody_columns: Option<B128>,
427 ) -> EngineApiResult<ForkchoiceUpdated> {
428 if let Some(custody_columns) = custody_columns {
429 self.inner.cell_custody.set_from_engine_api(custody_columns);
430 }
431 self.validate_and_execute_forkchoice(EngineApiMessageVersion::V4, state, payload_attrs)
432 .await
433 }
434
435 pub async fn fork_choice_updated_v4_metered(
437 &self,
438 state: ForkchoiceState,
439 payload_attrs: Option<EngineT::PayloadAttributes>,
440 custody_columns: Option<B128>,
441 ) -> EngineApiResult<ForkchoiceUpdated> {
442 let start = Instant::now();
443 let res = Self::fork_choice_updated_v4(self, state, payload_attrs, custody_columns).await;
444 self.inner.metrics.latency.fork_choice_updated_v4.record(start.elapsed());
445 res
446 }
447
448 pub async fn fork_choice_updated_v5(
452 &self,
453 state: ForkchoiceState,
454 payload_attrs: Option<EngineT::PayloadAttributes>,
455 custody_columns: Option<B128>,
456 ) -> EngineApiResult<ForkchoiceUpdatedResponseV2> {
457 if let Some(custody_columns) = custody_columns {
458 self.inner.cell_custody.set_from_engine_api(custody_columns);
459 }
460
461 Ok(self
463 .validate_and_execute_forkchoice(EngineApiMessageVersion::V5, state, payload_attrs)
464 .await?
465 .into())
466 }
467
468 pub async fn fork_choice_updated_v5_metered(
470 &self,
471 state: ForkchoiceState,
472 payload_attrs: Option<EngineT::PayloadAttributes>,
473 custody_columns: Option<B128>,
474 ) -> EngineApiResult<ForkchoiceUpdatedResponseV2> {
475 let start = Instant::now();
476 let res = Self::fork_choice_updated_v5(self, state, payload_attrs, custody_columns).await;
477 self.inner.metrics.latency.fork_choice_updated_v5.record(start.elapsed());
478 res
479 }
480
481 pub fn get_inclusion_list_v1(&self) -> EngineApiResult<Vec<Bytes>> {
483 let mut total_size = 0;
484 let mut inclusion_list = Vec::new();
485
486 for pool_tx in self.inner.tx_pool.best_transactions().without_blobs().without_updates() {
487 let encoded = pool_tx.encoded_2718_consensus();
488 let new_size = total_size + encoded.len();
489 if new_size > MAX_BYTES_PER_INCLUSION_LIST as usize {
490 break
491 }
492
493 total_size = new_size;
494 inclusion_list.push(encoded);
495 }
496
497 Ok(inclusion_list)
498 }
499
500 pub fn get_inclusion_list_v1_metered(&self) -> EngineApiResult<Vec<Bytes>> {
502 let start = Instant::now();
503 let result = Self::get_inclusion_list_v1(self);
504 self.inner.metrics.latency.get_inclusion_list_v1.record(start.elapsed());
505 result
506 }
507
508 async fn get_built_payload(
510 &self,
511 payload_id: PayloadId,
512 ) -> EngineApiResult<EngineT::BuiltPayload> {
513 self.inner
514 .payload_store
515 .resolve(payload_id)
516 .await
517 .ok_or(EngineApiError::UnknownPayload)?
518 .map_err(|_| EngineApiError::UnknownPayload)
519 }
520
521 async fn get_payload_inner<R>(
524 &self,
525 payload_id: PayloadId,
526 version: EngineApiMessageVersion,
527 ) -> EngineApiResult<R>
528 where
529 EngineT::BuiltPayload: TryInto<R>,
530 {
531 let timestamp = self.get_payload_timestamp(payload_id).await?;
534 validate_payload_timestamp(
535 &self.inner.chain_spec,
536 version,
537 timestamp,
538 MessageValidationKind::GetPayload,
539 )?;
540
541 self.get_built_payload(payload_id).await?.try_into().map_err(|_| {
543 warn!(?version, "could not transform built payload");
544 EngineApiError::UnknownPayload
545 })
546 }
547
548 pub async fn get_payload_v1(
558 &self,
559 payload_id: PayloadId,
560 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV1> {
561 self.get_built_payload(payload_id).await?.try_into().map_err(|_| {
562 warn!(version = ?EngineApiMessageVersion::V1, "could not transform built payload");
563 EngineApiError::UnknownPayload
564 })
565 }
566
567 pub async fn get_payload_v1_metered(
569 &self,
570 payload_id: PayloadId,
571 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV1> {
572 let start = Instant::now();
573 let res = Self::get_payload_v1(self, payload_id).await;
574 self.inner.metrics.latency.get_payload_v1.record(start.elapsed());
575 res
576 }
577
578 pub async fn get_payload_v2(
586 &self,
587 payload_id: PayloadId,
588 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV2> {
589 self.get_payload_inner(payload_id, EngineApiMessageVersion::V2).await
590 }
591
592 pub async fn get_payload_v2_metered(
594 &self,
595 payload_id: PayloadId,
596 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV2> {
597 let start = Instant::now();
598 let res = Self::get_payload_v2(self, payload_id).await;
599 self.inner.metrics.latency.get_payload_v2.record(start.elapsed());
600 res
601 }
602
603 pub async fn get_payload_v3(
611 &self,
612 payload_id: PayloadId,
613 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV3> {
614 self.get_payload_inner(payload_id, EngineApiMessageVersion::V3).await
615 }
616
617 pub async fn get_payload_v3_metered(
619 &self,
620 payload_id: PayloadId,
621 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV3> {
622 let start = Instant::now();
623 let res = Self::get_payload_v3(self, payload_id).await;
624 self.inner.metrics.latency.get_payload_v3.record(start.elapsed());
625 res
626 }
627
628 pub async fn get_payload_v4(
636 &self,
637 payload_id: PayloadId,
638 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV4> {
639 self.get_payload_inner(payload_id, EngineApiMessageVersion::V4).await
640 }
641
642 pub async fn get_payload_v4_metered(
644 &self,
645 payload_id: PayloadId,
646 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV4> {
647 let start = Instant::now();
648 let res = Self::get_payload_v4(self, payload_id).await;
649 self.inner.metrics.latency.get_payload_v4.record(start.elapsed());
650 res
651 }
652
653 pub async fn get_payload_v5(
663 &self,
664 payload_id: PayloadId,
665 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV5> {
666 self.get_payload_inner(payload_id, EngineApiMessageVersion::V5).await
667 }
668
669 pub async fn get_payload_v5_metered(
671 &self,
672 payload_id: PayloadId,
673 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV5> {
674 let start = Instant::now();
675 let res = Self::get_payload_v5(self, payload_id).await;
676 self.inner.metrics.latency.get_payload_v5.record(start.elapsed());
677 res
678 }
679
680 pub async fn get_payload_v6(
686 &self,
687 payload_id: PayloadId,
688 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV6> {
689 self.get_payload_inner(payload_id, EngineApiMessageVersion::V6).await
690 }
691
692 pub async fn get_payload_v6_metered(
694 &self,
695 payload_id: PayloadId,
696 ) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV6> {
697 let start = Instant::now();
698 let res = Self::get_payload_v6(self, payload_id).await;
699 self.inner.metrics.latency.get_payload_v6.record(start.elapsed());
700 res
701 }
702
703 pub async fn get_payload_bodies_by_range_with<F, R>(
706 &self,
707 start: BlockNumber,
708 count: u64,
709 f: F,
710 ) -> EngineApiResult<Vec<Option<R>>>
711 where
712 F: Fn(Provider::Block) -> R + Send + 'static,
713 R: Send + 'static,
714 {
715 let (tx, rx) = oneshot::channel();
716 let inner = self.inner.clone();
717
718 self.inner.task_spawner.spawn_blocking_task(async move {
719 if count > MAX_PAYLOAD_BODIES_LIMIT {
720 tx.send(Err(EngineApiError::PayloadRequestTooLarge { len: count })).ok();
721 return;
722 }
723
724 if start == 0 || count == 0 {
725 tx.send(Err(EngineApiError::InvalidBodiesRange { start, count })).ok();
726 return;
727 }
728
729 let mut result = Vec::with_capacity(count as usize);
730
731 let mut end = start.saturating_add(count - 1);
733
734 if let Ok(best_block) = inner.provider.best_block_number()
737 && end > best_block {
738 end = best_block;
739 }
740
741 let earliest_block = inner.provider.earliest_block_number().unwrap_or(0);
743 for num in start..=end {
744 if tx.is_closed() {
745 return;
746 }
747
748 if num < earliest_block {
749 result.push(None);
750 continue;
751 }
752 let block_result = inner.provider.block(BlockHashOrNumber::Number(num));
753 match block_result {
754 Ok(block) => {
755 result.push(block.map(&f));
756 }
757 Err(err) => {
758 tx.send(Err(EngineApiError::Internal(Box::new(err)))).ok();
759 return;
760 }
761 };
762 }
763 tx.send(Ok(result)).ok();
764 });
765
766 rx.await.map_err(|err| EngineApiError::Internal(Box::new(err)))?
767 }
768
769 pub async fn get_payload_bodies_by_range_with_timestamps(
774 &self,
775 start: BlockNumber,
776 count: u64,
777 include_bal: bool,
778 ) -> EngineApiResult<Vec<Option<(u64, ExecutionPayloadBodyV2)>>> {
779 let bodies = self
780 .get_payload_bodies_by_range_with(start, count, Self::payload_body_with_timestamp)
781 .await?;
782 self.attach_payload_body_bals(bodies, include_bal).await
783 }
784
785 pub async fn get_payload_bodies_by_range_with_timestamps_metered(
787 &self,
788 start: BlockNumber,
789 count: u64,
790 include_bal: bool,
791 ) -> EngineApiResult<Vec<Option<(u64, ExecutionPayloadBodyV2)>>> {
792 let start_time = Instant::now();
793 let result =
794 self.get_payload_bodies_by_range_with_timestamps(start, count, include_bal).await;
795 let latency = &self.inner.metrics.latency;
796 if include_bal {
797 latency.get_payload_bodies_by_range_v2.record(start_time.elapsed());
798 } else {
799 latency.get_payload_bodies_by_range_v1.record(start_time.elapsed());
800 }
801 result
802 }
803
804 pub async fn get_payload_bodies_by_range_v1(
815 &self,
816 start: BlockNumber,
817 count: u64,
818 ) -> EngineApiResult<ExecutionPayloadBodiesV1> {
819 self.get_payload_bodies_by_range_with(start, count, |block| ExecutionPayloadBodyV1 {
820 transactions: block.body().encoded_2718_transactions(),
821 withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner),
822 })
823 .await
824 }
825
826 pub async fn get_payload_bodies_by_range_v1_metered(
828 &self,
829 start: BlockNumber,
830 count: u64,
831 ) -> EngineApiResult<ExecutionPayloadBodiesV1> {
832 let start_time = Instant::now();
833 let res = Self::get_payload_bodies_by_range_v1(self, start, count).await;
834 self.inner.metrics.latency.get_payload_bodies_by_range_v1.record(start_time.elapsed());
835 res
836 }
837
838 pub async fn get_payload_bodies_by_range_v2(
842 &self,
843 start: BlockNumber,
844 count: u64,
845 ) -> EngineApiResult<ExecutionPayloadBodiesV2> {
846 Ok(self
847 .get_payload_bodies_by_range_with_timestamps(start, count, true)
848 .await?
849 .into_iter()
850 .map(|body| body.map(|(_, body)| body))
851 .collect())
852 }
853
854 pub async fn get_payload_bodies_by_range_v2_metered(
856 &self,
857 start: BlockNumber,
858 count: u64,
859 ) -> EngineApiResult<ExecutionPayloadBodiesV2> {
860 let start_time = Instant::now();
861 let res = Self::get_payload_bodies_by_range_v2(self, start, count).await;
862 self.inner.metrics.latency.get_payload_bodies_by_range_v2.record(start_time.elapsed());
863 res
864 }
865
866 pub async fn get_payload_bodies_by_hash_with<F, R>(
868 &self,
869 hashes: Vec<BlockHash>,
870 f: F,
871 ) -> EngineApiResult<Vec<Option<R>>>
872 where
873 F: Fn(Provider::Block) -> R + Send + 'static,
874 R: Send + 'static,
875 {
876 let len = hashes.len() as u64;
877 if len > MAX_PAYLOAD_BODIES_LIMIT {
878 return Err(EngineApiError::PayloadRequestTooLarge { len });
879 }
880
881 let (tx, rx) = oneshot::channel();
882 let inner = self.inner.clone();
883
884 self.inner.task_spawner.spawn_blocking_task(async move {
885 let mut result = Vec::with_capacity(hashes.len());
886 for hash in hashes {
887 if tx.is_closed() {
888 return;
889 }
890
891 let block_result = inner.provider.block(BlockHashOrNumber::Hash(hash));
892 match block_result {
893 Ok(block) => {
894 result.push(block.map(&f));
895 }
896 Err(err) => {
897 let _ = tx.send(Err(EngineApiError::Internal(Box::new(err))));
898 return;
899 }
900 }
901 }
902 tx.send(Ok(result)).ok();
903 });
904
905 rx.await.map_err(|err| EngineApiError::Internal(Box::new(err)))?
906 }
907
908 pub async fn get_payload_bodies_by_hash_with_timestamps(
910 &self,
911 hashes: Vec<BlockHash>,
912 include_bal: bool,
913 ) -> EngineApiResult<Vec<Option<(u64, ExecutionPayloadBodyV2)>>> {
914 let bodies =
915 self.get_payload_bodies_by_hash_with(hashes, Self::payload_body_with_timestamp).await?;
916 self.attach_payload_body_bals(bodies, include_bal).await
917 }
918
919 pub async fn get_payload_bodies_by_hash_with_timestamps_metered(
921 &self,
922 hashes: Vec<BlockHash>,
923 include_bal: bool,
924 ) -> EngineApiResult<Vec<Option<(u64, ExecutionPayloadBodyV2)>>> {
925 let start = Instant::now();
926 let result = self.get_payload_bodies_by_hash_with_timestamps(hashes, include_bal).await;
927 let latency = &self.inner.metrics.latency;
928 if include_bal {
929 latency.get_payload_bodies_by_hash_v2.record(start.elapsed());
930 } else {
931 latency.get_payload_bodies_by_hash_v1.record(start.elapsed());
932 }
933 result
934 }
935
936 fn payload_body_with_timestamp(
937 block: Provider::Block,
938 ) -> (BlockHash, u64, ExecutionPayloadBodyV2) {
939 (
940 block.header().hash_slow(),
941 block.header().timestamp(),
942 ExecutionPayloadBodyV2 {
943 transactions: block.body().encoded_2718_transactions(),
944 withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner),
945 block_access_list: None,
946 },
947 )
948 }
949
950 async fn attach_payload_body_bals(
951 &self,
952 mut bodies: Vec<Option<(BlockHash, u64, ExecutionPayloadBodyV2)>>,
953 include_bal: bool,
954 ) -> EngineApiResult<Vec<Option<(u64, ExecutionPayloadBodyV2)>>> {
955 if include_bal {
956 let hashes = bodies.iter().flatten().map(|(hash, _, _)| *hash).collect();
957 let bals = self.get_block_access_lists_by_hashes(hashes).await?;
958 for ((_, _, body), bal) in bodies.iter_mut().flatten().zip(bals) {
959 body.block_access_list = bal;
960 }
961 }
962 Ok(bodies
963 .into_iter()
964 .map(|body| body.map(|(_, timestamp, body)| (timestamp, body)))
965 .collect())
966 }
967
968 async fn get_block_access_lists_by_hashes(
969 &self,
970 hashes: Vec<BlockHash>,
971 ) -> EngineApiResult<Vec<Option<Bytes>>> {
972 let len = hashes.len() as u64;
973 if len > MAX_PAYLOAD_BODIES_LIMIT {
974 return Err(EngineApiError::PayloadRequestTooLarge { len });
975 }
976
977 let (tx, rx) = oneshot::channel();
978 let inner = self.inner.clone();
979
980 self.inner.task_spawner.spawn_blocking_task(async move {
981 if tx.is_closed() {
982 return;
983 }
984
985 tx.send(
986 inner
987 .provider
988 .get_bals_by_hashes(&hashes)
989 .map_err(|err| EngineApiError::Internal(Box::new(err))),
990 )
991 .ok();
992 });
993
994 rx.await.map_err(|err| EngineApiError::Internal(Box::new(err)))?
995 }
996
997 pub async fn get_payload_bodies_by_hash_v1(
999 &self,
1000 hashes: Vec<BlockHash>,
1001 ) -> EngineApiResult<ExecutionPayloadBodiesV1> {
1002 self.get_payload_bodies_by_hash_with(hashes, |block| ExecutionPayloadBodyV1 {
1003 transactions: block.body().encoded_2718_transactions(),
1004 withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner),
1005 })
1006 .await
1007 }
1008
1009 pub async fn get_payload_bodies_by_hash_v1_metered(
1011 &self,
1012 hashes: Vec<BlockHash>,
1013 ) -> EngineApiResult<ExecutionPayloadBodiesV1> {
1014 let start = Instant::now();
1015 let res = Self::get_payload_bodies_by_hash_v1(self, hashes).await;
1016 self.inner.metrics.latency.get_payload_bodies_by_hash_v1.record(start.elapsed());
1017 res
1018 }
1019
1020 pub async fn get_payload_bodies_by_hash_v2(
1024 &self,
1025 hashes: Vec<BlockHash>,
1026 ) -> EngineApiResult<ExecutionPayloadBodiesV2> {
1027 let payload_bodies =
1028 self.get_payload_bodies_by_hash_with(hashes.clone(), |block| ExecutionPayloadBodyV2 {
1029 transactions: block.body().encoded_2718_transactions(),
1030 withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner),
1031 block_access_list: None,
1032 });
1033 let block_access_lists = self.get_block_access_lists_by_hashes(hashes);
1034 let (mut payload_bodies, block_access_lists) =
1035 tokio::try_join!(payload_bodies, block_access_lists)?;
1036
1037 for (payload_body, block_access_list) in payload_bodies.iter_mut().zip(block_access_lists) {
1038 if let Some(payload_body) = payload_body {
1039 payload_body.block_access_list = block_access_list;
1040 }
1041 }
1042
1043 Ok(payload_bodies)
1044 }
1045
1046 pub async fn get_payload_bodies_by_hash_v2_metered(
1048 &self,
1049 hashes: Vec<BlockHash>,
1050 ) -> EngineApiResult<ExecutionPayloadBodiesV2> {
1051 let start = Instant::now();
1052 let res = Self::get_payload_bodies_by_hash_v2(self, hashes).await;
1053 self.inner.metrics.latency.get_payload_bodies_by_hash_v2.record(start.elapsed());
1054 res
1055 }
1056
1057 async fn validate_and_execute_forkchoice(
1071 &self,
1072 version: EngineApiMessageVersion,
1073 state: ForkchoiceState,
1074 payload_attrs: Option<EngineT::PayloadAttributes>,
1075 ) -> EngineApiResult<ForkchoiceUpdated> {
1076 if let Some(ref attrs) = payload_attrs {
1077 let attr_validation_res =
1078 self.inner.validator.ensure_well_formed_attributes(version, attrs);
1079
1080 if let Err(err) = attr_validation_res {
1090 let fcu_res = self.inner.beacon_consensus.fork_choice_updated(state, None).await?;
1091 if fcu_res.is_invalid() || fcu_res.payload_status.is_syncing() {
1092 return Ok(fcu_res)
1093 }
1094 return Err(err.into())
1095 }
1096 }
1097
1098 Ok(self.inner.beacon_consensus.fork_choice_updated(state, payload_attrs).await?)
1099 }
1100
1101 pub fn capabilities(&self) -> &EngineCapabilities {
1103 &self.inner.capabilities
1104 }
1105
1106 fn has_blobs(&self, versioned_hashes: Vec<B256>) -> EngineApiResult<Vec<bool>> {
1107 if versioned_hashes.len() > MAX_BLOB_LIMIT {
1108 return Err(EngineApiError::BlobRequestTooLarge { len: versioned_hashes.len() })
1109 }
1110
1111 self.inner
1112 .tx_pool
1113 .has_blobs_for_versioned_hashes(&versioned_hashes)
1114 .map_err(|err| EngineApiError::Internal(Box::new(err)))
1115 }
1116
1117 pub fn has_blobs_metered(&self, versioned_hashes: Vec<B256>) -> EngineApiResult<Vec<bool>> {
1119 let start = Instant::now();
1120 let res = Self::has_blobs(self, versioned_hashes);
1121 self.inner.metrics.latency.has_blobs.record(start.elapsed());
1122 res
1123 }
1124
1125 fn get_blobs_v1(
1126 &self,
1127 versioned_hashes: Vec<B256>,
1128 ) -> EngineApiResult<Vec<Option<BlobAndProofV1>>> {
1129 let current_timestamp =
1131 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs();
1132 if self.inner.chain_spec.is_osaka_active_at_timestamp(current_timestamp) {
1133 return Err(EngineApiError::EngineObjectValidationError(
1134 reth_payload_primitives::EngineObjectValidationError::UnsupportedFork,
1135 ));
1136 }
1137
1138 if versioned_hashes.len() > MAX_BLOB_LIMIT {
1139 return Err(EngineApiError::BlobRequestTooLarge { len: versioned_hashes.len() })
1140 }
1141
1142 self.inner
1143 .tx_pool
1144 .get_blobs_for_versioned_hashes_v1(&versioned_hashes)
1145 .map_err(|err| EngineApiError::Internal(Box::new(err)))
1146 }
1147
1148 pub fn get_blobs_v1_metered(
1150 &self,
1151 versioned_hashes: Vec<B256>,
1152 ) -> EngineApiResult<Vec<Option<BlobAndProofV1>>> {
1153 let hashes_len = versioned_hashes.len();
1154 let start = Instant::now();
1155 let res = Self::get_blobs_v1(self, versioned_hashes);
1156 self.inner.metrics.latency.get_blobs_v1.record(start.elapsed());
1157
1158 if let Ok(blobs) = &res {
1159 let blobs_found = blobs.iter().flatten().count();
1160 let blobs_missed = hashes_len - blobs_found;
1161
1162 self.inner.metrics.blob_metrics.blob_count.increment(blobs_found as u64);
1163 self.inner.metrics.blob_metrics.blob_misses.increment(blobs_missed as u64);
1164 }
1165
1166 res
1167 }
1168
1169 fn get_blobs_v2(
1170 &self,
1171 versioned_hashes: Vec<B256>,
1172 ) -> EngineApiResult<Option<Vec<BlobAndProofV2>>> {
1173 let current_timestamp =
1175 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs();
1176 if !self.inner.chain_spec.is_osaka_active_at_timestamp(current_timestamp) {
1177 return Err(EngineApiError::EngineObjectValidationError(
1178 reth_payload_primitives::EngineObjectValidationError::UnsupportedFork,
1179 ));
1180 }
1181
1182 if versioned_hashes.len() > MAX_BLOB_LIMIT {
1183 return Err(EngineApiError::BlobRequestTooLarge { len: versioned_hashes.len() })
1184 }
1185
1186 self.inner
1187 .tx_pool
1188 .get_blobs_for_versioned_hashes_v2(&versioned_hashes)
1189 .map_err(|err| EngineApiError::Internal(Box::new(err)))
1190 }
1191
1192 fn get_blobs_v3(
1193 &self,
1194 versioned_hashes: Vec<B256>,
1195 ) -> EngineApiResult<Option<Vec<Option<BlobAndProofV2>>>> {
1196 let current_timestamp =
1198 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs();
1199 if !self.inner.chain_spec.is_osaka_active_at_timestamp(current_timestamp) {
1200 return Err(EngineApiError::EngineObjectValidationError(
1201 reth_payload_primitives::EngineObjectValidationError::UnsupportedFork,
1202 ));
1203 }
1204
1205 if versioned_hashes.len() > MAX_BLOB_LIMIT {
1206 return Err(EngineApiError::BlobRequestTooLarge { len: versioned_hashes.len() })
1207 }
1208
1209 if (*self.inner.is_syncing)() {
1211 return Ok(None)
1212 }
1213
1214 self.inner
1215 .tx_pool
1216 .get_blobs_for_versioned_hashes_v3(&versioned_hashes)
1217 .map(Some)
1218 .map_err(|err| EngineApiError::Internal(Box::new(err)))
1219 }
1220
1221 fn get_blobs_v4(
1222 &self,
1223 versioned_hashes: Vec<B256>,
1224 indices_bitarray: B128,
1225 ) -> EngineApiResult<Option<Vec<Option<BlobCellsAndProofsV1>>>> {
1226 let cell_mask = BlobCellMask::from_bits(u128::from_le_bytes(indices_bitarray.into()));
1228 let current_timestamp =
1229 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs();
1230 if !self.inner.chain_spec.is_osaka_active_at_timestamp(current_timestamp) {
1231 return Err(EngineApiError::EngineObjectValidationError(
1232 reth_payload_primitives::EngineObjectValidationError::UnsupportedFork,
1233 ));
1234 }
1235
1236 if versioned_hashes.len() > MAX_BLOB_LIMIT {
1237 return Err(EngineApiError::BlobRequestTooLarge { len: versioned_hashes.len() })
1238 }
1239
1240 if (*self.inner.is_syncing)() {
1242 return Ok(None)
1243 }
1244
1245 self.inner
1246 .tx_pool
1247 .get_blobs_for_versioned_hashes_v4(&versioned_hashes, cell_mask)
1248 .map(Some)
1249 .map_err(|err| EngineApiError::Internal(Box::new(err)))
1250 }
1251
1252 pub fn get_blobs_v2_metered(
1254 &self,
1255 versioned_hashes: Vec<B256>,
1256 ) -> EngineApiResult<Option<Vec<BlobAndProofV2>>> {
1257 let hashes_len = versioned_hashes.len();
1258 let start = Instant::now();
1259 let res = Self::get_blobs_v2(self, versioned_hashes);
1260 self.inner.metrics.latency.get_blobs_v2.record(start.elapsed());
1261
1262 if let Ok(blobs) = &res {
1263 let blobs_found = blobs.iter().flatten().count();
1264
1265 self.inner
1266 .metrics
1267 .blob_metrics
1268 .get_blobs_requests_blobs_total
1269 .increment(hashes_len as u64);
1270 self.inner
1271 .metrics
1272 .blob_metrics
1273 .get_blobs_requests_blobs_in_blobpool_total
1274 .increment(blobs_found as u64);
1275
1276 if blobs_found == hashes_len {
1277 self.inner.metrics.blob_metrics.get_blobs_requests_success_total.increment(1);
1278 } else {
1279 self.inner.metrics.blob_metrics.get_blobs_requests_failure_total.increment(1);
1280 }
1281 } else {
1282 self.inner.metrics.blob_metrics.get_blobs_requests_failure_total.increment(1);
1283 }
1284
1285 res
1286 }
1287
1288 pub fn get_blobs_v3_metered(
1290 &self,
1291 versioned_hashes: Vec<B256>,
1292 ) -> EngineApiResult<Option<Vec<Option<BlobAndProofV2>>>> {
1293 let hashes_len = versioned_hashes.len();
1294 let start = Instant::now();
1295 let res = Self::get_blobs_v3(self, versioned_hashes);
1296 self.inner.metrics.latency.get_blobs_v3.record(start.elapsed());
1297
1298 if let Ok(Some(blobs)) = &res {
1299 let blobs_found = blobs.iter().flatten().count();
1300 let blobs_missed = hashes_len - blobs_found;
1301
1302 self.inner.metrics.blob_metrics.blob_count.increment(blobs_found as u64);
1303 self.inner.metrics.blob_metrics.blob_misses.increment(blobs_missed as u64);
1304 }
1305
1306 res
1307 }
1308
1309 pub fn get_blobs_v4_metered(
1311 &self,
1312 versioned_hashes: Vec<B256>,
1313 indices_bitarray: B128,
1314 ) -> EngineApiResult<Option<Vec<Option<BlobCellsAndProofsV1>>>> {
1315 let hashes_len = versioned_hashes.len();
1316 let start = Instant::now();
1317 let res = Self::get_blobs_v4(self, versioned_hashes, indices_bitarray);
1318 self.inner.metrics.latency.get_blobs_v4.record(start.elapsed());
1319
1320 if let Ok(Some(blobs)) = &res {
1321 let blobs_found = blobs.iter().flatten().count();
1322 let blobs_missed = hashes_len - blobs_found;
1323
1324 self.inner.metrics.blob_metrics.blob_count.increment(blobs_found as u64);
1325 self.inner.metrics.blob_metrics.blob_misses.increment(blobs_missed as u64);
1326 }
1327
1328 res
1329 }
1330}
1331
1332impl<Provider, EngineT, Pool, Validator, ChainSpec> EngineApiServer<EngineT>
1334 for EngineApi<Provider, EngineT, Pool, Validator, ChainSpec>
1335where
1336 Provider: BlockReader + StateProviderFactory + BalProvider + 'static,
1337 EngineT: EngineTypes<ExecutionData = ExecutionData>,
1338 Pool: TransactionPool + 'static,
1339 Validator: EngineApiValidator<EngineT>,
1340 ChainSpec: EthereumHardforks + Send + Sync + 'static,
1341{
1342 async fn new_payload_v1(&self, payload: ExecutionPayloadV1) -> RpcResult<PayloadStatus> {
1346 trace!(target: "rpc::engine", "Serving engine_newPayloadV1");
1347 let payload =
1348 ExecutionData { payload: payload.into(), sidecar: ExecutionPayloadSidecar::none() };
1349 Ok(self.new_payload_v1_metered(payload).await?)
1350 }
1351
1352 async fn new_payload_v2(&self, payload: ExecutionPayloadInputV2) -> RpcResult<PayloadStatus> {
1355 trace!(target: "rpc::engine", "Serving engine_newPayloadV2");
1356 let payload = ExecutionData {
1357 payload: payload.into_payload(),
1358 sidecar: ExecutionPayloadSidecar::none(),
1359 };
1360
1361 Ok(self.new_payload_v2_metered(payload).await?)
1362 }
1363
1364 async fn new_payload_v3(
1367 &self,
1368 payload: ExecutionPayloadV3,
1369 versioned_hashes: Vec<B256>,
1370 parent_beacon_block_root: B256,
1371 ) -> RpcResult<PayloadStatus> {
1372 trace!(target: "rpc::engine", "Serving engine_newPayloadV3");
1373 let payload = ExecutionData {
1374 payload: payload.into(),
1375 sidecar: ExecutionPayloadSidecar::v3(CancunPayloadFields {
1376 versioned_hashes,
1377 parent_beacon_block_root,
1378 }),
1379 };
1380
1381 self.new_payload_v3_metered(payload).await
1382 }
1383
1384 async fn new_payload_v4(
1387 &self,
1388 payload: ExecutionPayloadV3,
1389 versioned_hashes: Vec<B256>,
1390 parent_beacon_block_root: B256,
1391 requests: RequestsOrHash,
1392 ) -> RpcResult<PayloadStatus> {
1393 trace!(target: "rpc::engine", "Serving engine_newPayloadV4");
1394
1395 if requests.is_hash() && !self.inner.accept_execution_requests_hash {
1397 return Err(EngineApiError::UnexpectedRequestsHash.into());
1398 }
1399
1400 let payload = ExecutionData {
1401 payload: payload.into(),
1402 sidecar: ExecutionPayloadSidecar::v4(
1403 CancunPayloadFields { versioned_hashes, parent_beacon_block_root },
1404 PraguePayloadFields { requests },
1405 ),
1406 };
1407
1408 self.new_payload_v4_metered(payload).await
1409 }
1410
1411 async fn new_payload_v5(
1417 &self,
1418 payload: ExecutionPayloadV4,
1419 versioned_hashes: Vec<B256>,
1420 parent_beacon_block_root: B256,
1421 requests: RequestsOrHash,
1422 ) -> RpcResult<PayloadStatus> {
1423 trace!(target: "rpc::engine", "Serving engine_newPayloadV5");
1424 if requests.is_hash() && !self.inner.accept_execution_requests_hash {
1426 return Err(EngineApiError::UnexpectedRequestsHash.into());
1427 }
1428
1429 let payload = ExecutionData {
1430 payload: payload.into(),
1431 sidecar: ExecutionPayloadSidecar::v4(
1432 CancunPayloadFields { versioned_hashes, parent_beacon_block_root },
1433 PraguePayloadFields { requests },
1434 ),
1435 };
1436
1437 self.new_payload_v5_metered(payload).await
1438 }
1439
1440 async fn new_payload_v6(
1444 &self,
1445 payload: ExecutionPayloadV4,
1446 versioned_hashes: Vec<B256>,
1447 parent_beacon_block_root: B256,
1448 execution_requests: RequestsOrHash,
1449 inclusion_list_transactions: Vec<Bytes>,
1450 ) -> RpcResult<PayloadStatusV2> {
1451 trace!(target: "rpc::engine", "Serving engine_newPayloadV6");
1452 if execution_requests.is_hash() && !self.inner.accept_execution_requests_hash {
1453 return Err(EngineApiError::UnexpectedRequestsHash.into());
1454 }
1455
1456 let payload = ExecutionData {
1457 payload: payload.into(),
1458 sidecar: ExecutionPayloadSidecar::v6(
1459 CancunPayloadFields { versioned_hashes, parent_beacon_block_root },
1460 PraguePayloadFields { requests: execution_requests },
1461 BogotaPayloadFields { inclusion_list_transactions },
1462 ),
1463 };
1464
1465 Ok(self.new_payload_v6_metered(payload).await?)
1468 }
1469
1470 async fn fork_choice_updated_v1(
1475 &self,
1476 fork_choice_state: ForkchoiceState,
1477 payload_attributes: Option<EngineT::PayloadAttributes>,
1478 ) -> RpcResult<ForkchoiceUpdated> {
1479 trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV1");
1480 Ok(self.fork_choice_updated_v1_metered(fork_choice_state, payload_attributes).await?)
1481 }
1482
1483 async fn fork_choice_updated_v2(
1486 &self,
1487 fork_choice_state: ForkchoiceState,
1488 payload_attributes: Option<EngineT::PayloadAttributes>,
1489 ) -> RpcResult<ForkchoiceUpdated> {
1490 trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV2");
1491 Ok(self.fork_choice_updated_v2_metered(fork_choice_state, payload_attributes).await?)
1492 }
1493
1494 async fn fork_choice_updated_v3(
1498 &self,
1499 fork_choice_state: ForkchoiceState,
1500 payload_attributes: Option<EngineT::PayloadAttributes>,
1501 ) -> RpcResult<ForkchoiceUpdated> {
1502 trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV3");
1503 Ok(self.fork_choice_updated_v3_metered(fork_choice_state, payload_attributes).await?)
1504 }
1505
1506 async fn fork_choice_updated_v4(
1510 &self,
1511 fork_choice_state: ForkchoiceState,
1512 payload_attributes: Option<EngineT::PayloadAttributes>,
1513 custody_columns: Option<B128>,
1514 ) -> RpcResult<ForkchoiceUpdated> {
1515 trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV4");
1516 Ok(self
1517 .fork_choice_updated_v4_metered(fork_choice_state, payload_attributes, custody_columns)
1518 .await?)
1519 }
1520
1521 async fn fork_choice_updated_v5(
1525 &self,
1526 fork_choice_state: ForkchoiceState,
1527 payload_attributes: Option<EngineT::PayloadAttributes>,
1528 custody_columns: Option<B128>,
1529 ) -> RpcResult<ForkchoiceUpdatedResponseV2> {
1530 trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV5");
1531 Ok(self
1532 .fork_choice_updated_v5_metered(fork_choice_state, payload_attributes, custody_columns)
1533 .await?)
1534 }
1535
1536 async fn get_payload_v1(
1548 &self,
1549 payload_id: PayloadId,
1550 ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV1> {
1551 trace!(target: "rpc::engine", "Serving engine_getPayloadV1");
1552 Ok(self.get_payload_v1_metered(payload_id).await?)
1553 }
1554
1555 async fn get_payload_v2(
1565 &self,
1566 payload_id: PayloadId,
1567 ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV2> {
1568 debug!(target: "rpc::engine", id = %payload_id, "Serving engine_getPayloadV2");
1569 Ok(self.get_payload_v2_metered(payload_id).await?)
1570 }
1571
1572 async fn get_payload_v3(
1582 &self,
1583 payload_id: PayloadId,
1584 ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV3> {
1585 trace!(target: "rpc::engine", "Serving engine_getPayloadV3");
1586 Ok(self.get_payload_v3_metered(payload_id).await?)
1587 }
1588
1589 async fn get_payload_v4(
1599 &self,
1600 payload_id: PayloadId,
1601 ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV4> {
1602 trace!(target: "rpc::engine", "Serving engine_getPayloadV4");
1603 Ok(self.get_payload_v4_metered(payload_id).await?)
1604 }
1605
1606 async fn get_payload_v5(
1616 &self,
1617 payload_id: PayloadId,
1618 ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV5> {
1619 trace!(target: "rpc::engine", "Serving engine_getPayloadV5");
1620 Ok(self.get_payload_v5_metered(payload_id).await?)
1621 }
1622
1623 async fn get_payload_v6(
1629 &self,
1630 payload_id: PayloadId,
1631 ) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV6> {
1632 trace!(target: "rpc::engine", "Serving engine_getPayloadV6");
1633 Ok(self.get_payload_v6_metered(payload_id).await?)
1634 }
1635
1636 async fn get_inclusion_list_v1(&self) -> RpcResult<Vec<Bytes>> {
1640 trace!(target: "rpc::engine", "Serving engine_getInclusionListV1");
1641 Ok(self.get_inclusion_list_v1_metered()?)
1642 }
1643
1644 async fn get_payload_bodies_by_hash_v1(
1647 &self,
1648 block_hashes: Vec<BlockHash>,
1649 ) -> RpcResult<ExecutionPayloadBodiesV1> {
1650 trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByHashV1");
1651 Ok(self.get_payload_bodies_by_hash_v1_metered(block_hashes).await?)
1652 }
1653
1654 async fn get_payload_bodies_by_hash_v2(
1660 &self,
1661 block_hashes: Vec<BlockHash>,
1662 ) -> RpcResult<ExecutionPayloadBodiesV2> {
1663 trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByHashV2");
1664 Ok(self.get_payload_bodies_by_hash_v2_metered(block_hashes).await?)
1665 }
1666
1667 async fn get_payload_bodies_by_range_v1(
1684 &self,
1685 start: U64,
1686 count: U64,
1687 ) -> RpcResult<ExecutionPayloadBodiesV1> {
1688 trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByRangeV1");
1689 Ok(self.get_payload_bodies_by_range_v1_metered(start.to(), count.to()).await?)
1690 }
1691
1692 async fn get_payload_bodies_by_range_v2(
1698 &self,
1699 start: U64,
1700 count: U64,
1701 ) -> RpcResult<ExecutionPayloadBodiesV2> {
1702 trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByRangeV2");
1703 Ok(self.get_payload_bodies_by_range_v2_metered(start.to(), count.to()).await?)
1704 }
1705
1706 async fn get_client_version_v1(
1710 &self,
1711 client: ClientVersionV1,
1712 ) -> RpcResult<Vec<ClientVersionV1>> {
1713 trace!(target: "rpc::engine", "Serving engine_getClientVersionV1");
1714 Ok(Self::get_client_version_v1(self, client)?)
1715 }
1716
1717 async fn exchange_capabilities(&self, capabilities: Vec<String>) -> RpcResult<Vec<String>> {
1720 trace!(target: "rpc::engine", "Serving engine_exchangeCapabilities");
1721
1722 let el_caps = self.capabilities();
1723 el_caps.log_capability_mismatches(&capabilities);
1724
1725 Ok(el_caps.list())
1726 }
1727
1728 async fn has_blobs(&self, versioned_hashes: Vec<B256>) -> RpcResult<Vec<bool>> {
1729 trace!(target: "rpc::engine", "Serving engine_hasBlobs");
1730 Ok(self.has_blobs_metered(versioned_hashes)?)
1731 }
1732
1733 async fn get_blobs_v1(
1734 &self,
1735 versioned_hashes: Vec<B256>,
1736 ) -> RpcResult<Vec<Option<BlobAndProofV1>>> {
1737 trace!(target: "rpc::engine", "Serving engine_getBlobsV1");
1738 Ok(self.get_blobs_v1_metered(versioned_hashes)?)
1739 }
1740
1741 async fn get_blobs_v2(
1742 &self,
1743 versioned_hashes: Vec<B256>,
1744 ) -> RpcResult<Option<Vec<BlobAndProofV2>>> {
1745 trace!(target: "rpc::engine", "Serving engine_getBlobsV2");
1746 Ok(self.get_blobs_v2_metered(versioned_hashes)?)
1747 }
1748
1749 async fn get_blobs_v3(
1750 &self,
1751 versioned_hashes: Vec<B256>,
1752 ) -> RpcResult<Option<Vec<Option<BlobAndProofV2>>>> {
1753 trace!(target: "rpc::engine", "Serving engine_getBlobsV3");
1754 Ok(self.get_blobs_v3_metered(versioned_hashes)?)
1755 }
1756
1757 async fn get_blobs_v4(
1758 &self,
1759 versioned_hashes: Vec<B256>,
1760 indices_bitarray: B128,
1761 ) -> RpcResult<Option<Vec<Option<BlobCellsAndProofsV1>>>> {
1762 trace!(target: "rpc::engine", "Serving engine_getBlobsV4");
1763 Ok(self.get_blobs_v4_metered(versioned_hashes, indices_bitarray)?)
1764 }
1765}
1766
1767impl<Provider, EngineT, Pool, Validator, ChainSpec> IntoEngineApiRpcModule
1768 for EngineApi<Provider, EngineT, Pool, Validator, ChainSpec>
1769where
1770 EngineT: EngineTypes,
1771 Self: EngineApiServer<EngineT>,
1772{
1773 fn into_rpc_module(self) -> RpcModule<()> {
1774 EngineApiServer::<EngineT>::into_rpc(self).remove_context()
1775 }
1776}
1777
1778impl<Provider, PayloadT, Pool, Validator, ChainSpec> std::fmt::Debug
1779 for EngineApi<Provider, PayloadT, Pool, Validator, ChainSpec>
1780where
1781 PayloadT: PayloadTypes,
1782{
1783 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1784 f.debug_struct("EngineApi").finish_non_exhaustive()
1785 }
1786}
1787
1788impl<Provider, PayloadT, Pool, Validator, ChainSpec> Clone
1789 for EngineApi<Provider, PayloadT, Pool, Validator, ChainSpec>
1790where
1791 PayloadT: PayloadTypes,
1792{
1793 fn clone(&self) -> Self {
1794 Self { inner: Arc::clone(&self.inner) }
1795 }
1796}
1797
1798struct EngineApiInner<Provider, PayloadT: PayloadTypes, Pool, Validator, ChainSpec> {
1800 provider: Provider,
1802 chain_spec: Arc<ChainSpec>,
1804 beacon_consensus: ConsensusEngineHandle<PayloadT>,
1806 payload_store: PayloadStore<PayloadT>,
1808 task_spawner: Runtime,
1810 metrics: EngineApiMetrics,
1812 client: ClientVersionV1,
1814 capabilities: EngineCapabilities,
1816 tx_pool: Pool,
1818 validator: Validator,
1820 accept_execution_requests_hash: bool,
1821 cell_custody: CellCustody,
1823 is_syncing: Arc<dyn Fn() -> bool + Send + Sync>,
1825}
1826
1827#[cfg(test)]
1828mod tests {
1829 use super::*;
1830 use crate::error::UNSUPPORTED_FORK_CODE;
1831 use alloy_eips::{eip7685::Requests, Encodable2718, NumHash};
1832 use alloy_primitives::{Address, Bytes, B256};
1833 use alloy_rpc_types_engine::{
1834 ClientCode, ClientVersionV1, ExecutionPayloadV2, PayloadAttributes, PayloadStatusEnum,
1835 };
1836 use assert_matches::assert_matches;
1837 use jsonrpsee_types::ErrorObject;
1838 use reth_chainspec::{ChainSpec, ChainSpecBuilder, MAINNET};
1839 use reth_engine_primitives::{BeaconEngineMessage, OnForkChoiceUpdated};
1840 use reth_ethereum_engine_primitives::EthEngineTypes;
1841 use reth_ethereum_primitives::{Block, TransactionSigned};
1842 use reth_network_api::{
1843 noop::NoopNetwork, EthProtocolInfo, NetworkError, NetworkInfo, NetworkStatus,
1844 };
1845 use reth_node_ethereum::EthereumEngineValidator;
1846 use reth_payload_builder::test_utils::spawn_test_payload_service;
1847 use reth_payload_primitives::EngineObjectValidationError;
1848 use reth_primitives_traits::SignedTransaction;
1849 use reth_provider::{test_utils::MockEthProvider, BalStoreHandle, InMemoryBalStore, RawBal};
1850 use reth_tasks::Runtime;
1851 use reth_transaction_pool::{
1852 blobstore::InMemoryBlobStore,
1853 noop::NoopTransactionPool,
1854 test_utils::{OkValidator, TransactionBuilder},
1855 CoinbaseTipOrdering, EthPooledTransaction, Pool, PoolTransaction, TransactionOrigin,
1856 };
1857 use std::time::Duration;
1858 use tokio::sync::mpsc::{error::TryRecvError, unbounded_channel, UnboundedReceiver};
1859
1860 type EthTestPool = Pool<
1861 OkValidator<EthPooledTransaction>,
1862 CoinbaseTipOrdering<EthPooledTransaction>,
1863 InMemoryBlobStore,
1864 >;
1865
1866 fn eth_test_pool() -> EthTestPool {
1867 Pool::new(
1868 OkValidator::default(),
1869 CoinbaseTipOrdering::default(),
1870 InMemoryBlobStore::default(),
1871 Default::default(),
1872 )
1873 }
1874
1875 fn pooled_transaction(transaction: TransactionSigned) -> EthPooledTransaction {
1876 let transaction = transaction.try_into_recovered().unwrap();
1877 let encoded_length = transaction.encode_2718_len();
1878 EthPooledTransaction::new(transaction, encoded_length)
1879 }
1880
1881 fn setup_engine_api() -> (
1882 EngineApiTestHandle,
1883 EngineApi<
1884 Arc<MockEthProvider>,
1885 EthEngineTypes,
1886 NoopTransactionPool,
1887 EthereumEngineValidator,
1888 ChainSpec,
1889 >,
1890 ) {
1891 setup_engine_api_with_pool(NoopTransactionPool::default())
1892 }
1893
1894 fn setup_engine_api_with_pool<Pool>(
1895 tx_pool: Pool,
1896 ) -> (
1897 EngineApiTestHandle,
1898 EngineApi<Arc<MockEthProvider>, EthEngineTypes, Pool, EthereumEngineValidator, ChainSpec>,
1899 )
1900 where
1901 Pool: TransactionPool + 'static,
1902 {
1903 setup_engine_api_with_provider(tx_pool, MockEthProvider::default())
1904 }
1905
1906 fn setup_engine_api_with_provider<Pool>(
1907 tx_pool: Pool,
1908 provider: MockEthProvider,
1909 ) -> (
1910 EngineApiTestHandle,
1911 EngineApi<Arc<MockEthProvider>, EthEngineTypes, Pool, EthereumEngineValidator, ChainSpec>,
1912 )
1913 where
1914 Pool: TransactionPool + 'static,
1915 {
1916 let client = ClientVersionV1 {
1917 code: ClientCode::RH,
1918 name: "Reth".to_string(),
1919 version: "v0.2.0-beta.5".to_string(),
1920 commit: "defa64b2".to_string(),
1921 };
1922
1923 let chain_spec: Arc<ChainSpec> = MAINNET.clone();
1924 let provider = Arc::new(provider);
1925 let payload_store = spawn_test_payload_service();
1926 let (to_engine, engine_rx) = unbounded_channel();
1927 let task_executor = Runtime::test();
1928 let api = EngineApi::new(
1929 provider.clone(),
1930 chain_spec.clone(),
1931 ConsensusEngineHandle::new(to_engine),
1932 payload_store.into(),
1933 tx_pool,
1934 task_executor,
1935 client,
1936 EngineCapabilities::default(),
1937 EthereumEngineValidator::new(chain_spec.clone()),
1938 false,
1939 NoopNetwork::default(),
1940 );
1941 let handle = EngineApiTestHandle { chain_spec, provider, from_api: engine_rx };
1942 (handle, api)
1943 }
1944
1945 #[tokio::test]
1946 async fn engine_client_version_v1() {
1947 let client = ClientVersionV1 {
1948 code: ClientCode::RH,
1949 name: "Reth".to_string(),
1950 version: "v0.2.0-beta.5".to_string(),
1951 commit: "defa64b2".to_string(),
1952 };
1953 let (_, api) = setup_engine_api();
1954 let res = api.get_client_version_v1(client.clone());
1955 assert_eq!(res.unwrap(), vec![client]);
1956 }
1957
1958 #[tokio::test]
1959 async fn get_inclusion_list_v1_stops_at_size_limit() {
1960 let pool = eth_test_pool();
1961 let first = pooled_transaction(
1962 TransactionBuilder::default()
1963 .max_fee_per_gas(3_000_000_000u128)
1964 .input(vec![0; 4_200])
1965 .into_legacy(),
1966 );
1967 let second = pooled_transaction(
1968 TransactionBuilder::default()
1969 .max_fee_per_gas(2_000_000_000u128)
1970 .input(vec![0; 4_200])
1971 .into_legacy(),
1972 );
1973 let third = pooled_transaction(
1974 TransactionBuilder::default().max_fee_per_gas(1_000_000_000u128).into_legacy(),
1975 );
1976 let expected = first.encoded_2718_consensus();
1977
1978 pool.add_transaction(TransactionOrigin::External, first).await.unwrap();
1979 pool.add_transaction(TransactionOrigin::External, second).await.unwrap();
1980 pool.add_transaction(TransactionOrigin::External, third).await.unwrap();
1981 let (_, api) = setup_engine_api_with_pool(pool);
1982
1983 let res = EngineApiServer::get_inclusion_list_v1(&api).await.unwrap();
1984 assert_eq!(res, vec![expected]);
1985 assert!(
1986 res.iter().map(|tx| tx.len()).sum::<usize>() <= MAX_BYTES_PER_INCLUSION_LIST as usize
1987 );
1988 }
1989
1990 fn inclusion_list_transaction(size: usize, typed: bool, fee: u128) -> EthPooledTransaction {
1992 for input_size in size.saturating_sub(256)..size {
1993 let builder = TransactionBuilder::default()
1994 .signer(B256::with_last_byte(if typed { 2 } else { 1 }))
1995 .max_fee_per_gas(fee)
1996 .max_priority_fee_per_gas(fee)
1997 .input(vec![0; input_size]);
1998 let transaction = if typed { builder.into_eip1559() } else { builder.into_legacy() };
1999 if transaction.encode_2718_len() == size {
2000 return pooled_transaction(transaction)
2001 }
2002 }
2003 panic!("could not construct a transaction of {size} bytes")
2004 }
2005
2006 #[tokio::test]
2007 async fn get_inclusion_list_v1_transaction_byte_boundaries() {
2008 let limit = MAX_BYTES_PER_INCLUSION_LIST as usize;
2009 for typed in [false, true] {
2010 for size in [limit - 3, limit, limit + 1] {
2011 let pool = eth_test_pool();
2012 let transaction = inclusion_list_transaction(size, typed, 3_000_000_000);
2013 let encoded = transaction.encoded_2718_consensus();
2014 assert_eq!(encoded.len(), size);
2015 pool.add_transaction(TransactionOrigin::External, transaction).await.unwrap();
2016 let (_, api) = setup_engine_api_with_pool(pool);
2017
2018 let res = EngineApiServer::get_inclusion_list_v1(&api).await.unwrap();
2019 let expected = if size <= limit { vec![encoded] } else { vec![] };
2020 assert_eq!(res, expected, "typed={typed}, size={size}");
2021 assert!(res.iter().all(|tx| !tx.is_empty()));
2022 }
2023 }
2024 }
2025
2026 #[tokio::test]
2027 async fn get_inclusion_list_v1_cumulative_transaction_byte_boundaries() {
2028 let limit = MAX_BYTES_PER_INCLUSION_LIST as usize;
2029 for extra in [0, 1] {
2030 let pool = eth_test_pool();
2031 let first = inclusion_list_transaction(limit / 2, false, 3_000_000_000);
2032 let second = inclusion_list_transaction(limit / 2 + extra, true, 2_000_000_000);
2033 let mut expected =
2034 vec![first.encoded_2718_consensus(), second.encoded_2718_consensus()];
2035 assert_eq!(expected.iter().map(|tx| tx.len()).sum::<usize>(), limit + extra);
2036 if extra != 0 {
2037 expected.pop();
2038 }
2039 pool.add_transaction(TransactionOrigin::External, first).await.unwrap();
2040 pool.add_transaction(TransactionOrigin::External, second).await.unwrap();
2041 let (_, api) = setup_engine_api_with_pool(pool);
2042
2043 let res = EngineApiServer::get_inclusion_list_v1(&api).await.unwrap();
2044 assert_eq!(res, expected);
2045 assert!(res.iter().all(|tx| !tx.is_empty()));
2046 }
2047 }
2048
2049 #[tokio::test]
2050 async fn get_inclusion_list_v1_excludes_blob_transactions() {
2051 let pool = eth_test_pool();
2052 let blob = pooled_transaction(
2053 TransactionBuilder::default()
2054 .max_fee_per_gas(2_000_000_000u128)
2055 .max_priority_fee_per_gas(1_000_000_000u128)
2056 .into_eip4844(),
2057 );
2058 let non_blob = pooled_transaction(
2059 TransactionBuilder::default()
2060 .max_fee_per_gas(1_000_000_000u128)
2061 .max_priority_fee_per_gas(1_000_000_000u128)
2062 .into_eip1559(),
2063 );
2064 let expected = non_blob.encoded_2718_consensus();
2065
2066 pool.add_transaction(TransactionOrigin::External, blob).await.unwrap();
2067 pool.add_transaction(TransactionOrigin::External, non_blob).await.unwrap();
2068 let (_, api) = setup_engine_api_with_pool(pool);
2069
2070 let res = EngineApiServer::get_inclusion_list_v1(&api).await.unwrap();
2071 assert_eq!(res, vec![expected]);
2072 }
2073
2074 #[tokio::test]
2075 async fn has_blobs_returns_ordered_availability() {
2076 let (_, api) = setup_engine_api();
2077
2078 let res = api.has_blobs_metered(vec![B256::ZERO, B256::with_last_byte(1)]).unwrap();
2079 assert_eq!(res, vec![false, false]);
2080 }
2081
2082 #[tokio::test]
2083 async fn has_blobs_rejects_large_requests() {
2084 let (_, api) = setup_engine_api();
2085
2086 let res = api.has_blobs_metered(vec![B256::ZERO; MAX_BLOB_LIMIT + 1]);
2087 assert_matches!(
2088 res,
2089 Err(EngineApiError::BlobRequestTooLarge { len }) if len == MAX_BLOB_LIMIT + 1
2090 );
2091 }
2092
2093 #[tokio::test]
2094 async fn payload_bodies_with_timestamps_preserve_missing_entries_and_head_truncation() {
2095 let mut provider = MockEthProvider::default();
2096 provider.bal_store = BalStoreHandle::new(InMemoryBalStore::default());
2097 let (handle, api) =
2098 setup_engine_api_with_provider(NoopTransactionPool::default(), provider);
2099 let mut block = Block::default();
2100 block.header.number = 1;
2101 block.header.timestamp = 123;
2102 let hash = block.header.hash_slow();
2103 handle.provider.add_block(hash, block);
2104 let raw_bal = Bytes::from_static(&[alloy_rlp::EMPTY_LIST_CODE]);
2105 handle
2106 .provider
2107 .bal_store
2108 .insert(NumHash::new(1, hash), RawBal::new(raw_bal.clone()))
2109 .unwrap();
2110 for include_bal in [false, true] {
2111 let by_hash = api
2112 .get_payload_bodies_by_hash_with_timestamps(
2113 vec![B256::ZERO, hash, hash],
2114 include_bal,
2115 )
2116 .await
2117 .unwrap();
2118 assert_eq!(by_hash.len(), 3);
2119 assert!(by_hash[0].is_none());
2120 assert_eq!(by_hash[1], by_hash[2]);
2121 let (timestamp, body) = by_hash[1].as_ref().unwrap();
2122 assert_eq!(*timestamp, 123);
2123 assert_eq!(body.block_access_list, include_bal.then(|| raw_bal.clone()));
2124 let by_range =
2125 api.get_payload_bodies_by_range_with_timestamps(1, 3, include_bal).await.unwrap();
2126 assert_eq!(by_range, vec![by_hash[1].clone()]);
2127 assert!(api
2128 .get_payload_bodies_by_range_with_timestamps(2, 3, include_bal)
2129 .await
2130 .unwrap()
2131 .is_empty());
2132 }
2133 }
2134
2135 #[tokio::test]
2136 async fn get_payload_bodies_by_hash_v2_returns_block_access_list_from_store() {
2137 let bal_store = BalStoreHandle::new(InMemoryBalStore::default());
2138 let mut provider = MockEthProvider::default();
2139 provider.bal_store = bal_store.clone();
2140 let provider = Arc::new(provider);
2141
2142 let client = ClientVersionV1 {
2143 code: ClientCode::RH,
2144 name: "Reth".to_string(),
2145 version: "v0.2.0-beta.5".to_string(),
2146 commit: "defa64b2".to_string(),
2147 };
2148 let chain_spec: Arc<ChainSpec> = MAINNET.clone();
2149 let payload_store = spawn_test_payload_service::<EthEngineTypes>();
2150 let (to_engine, _engine_rx) = unbounded_channel();
2151 let api = EngineApi::new(
2152 provider.clone(),
2153 chain_spec.clone(),
2154 ConsensusEngineHandle::new(to_engine),
2155 payload_store.into(),
2156 NoopTransactionPool::default(),
2157 Runtime::test(),
2158 client,
2159 EngineCapabilities::default(),
2160 EthereumEngineValidator::new(chain_spec),
2161 false,
2162 NoopNetwork::default(),
2163 );
2164
2165 let mut block = Block::default();
2166 block.header.number = 1;
2167 let block_hash = block.header.hash_slow();
2168 provider.add_block(block_hash, block);
2169
2170 let mut block_without_bal = Block::default();
2171 block_without_bal.header.number = 2;
2172 let block_without_bal_hash = block_without_bal.header.hash_slow();
2173 provider.add_block(block_without_bal_hash, block_without_bal);
2174
2175 let raw_bal = Bytes::from_static(&[alloy_rlp::EMPTY_LIST_CODE]);
2176 bal_store.insert(NumHash::new(1, block_hash), RawBal::new(raw_bal.clone())).unwrap();
2177
2178 let missing_hash = B256::with_last_byte(3);
2179 let response = api
2180 .get_payload_bodies_by_hash_v2(vec![block_hash, block_without_bal_hash, missing_hash])
2181 .await
2182 .unwrap();
2183
2184 assert_eq!(response.len(), 3);
2185 assert_eq!(response[0].as_ref().unwrap().block_access_list, Some(raw_bal));
2186 assert_eq!(response[1].as_ref().unwrap().block_access_list, None);
2187 assert!(response[2].is_none());
2188 }
2189
2190 #[tokio::test]
2191 async fn get_payload_bodies_by_range_v2_returns_block_access_lists_from_store() {
2192 let bal_store = BalStoreHandle::new(InMemoryBalStore::default());
2193 let mut provider = MockEthProvider::default();
2194 provider.bal_store = bal_store.clone();
2195 let provider = Arc::new(provider);
2196
2197 let client = ClientVersionV1 {
2198 code: ClientCode::RH,
2199 name: "Reth".to_string(),
2200 version: "v0.2.0-beta.5".to_string(),
2201 commit: "defa64b2".to_string(),
2202 };
2203 let chain_spec: Arc<ChainSpec> = MAINNET.clone();
2204 let payload_store = spawn_test_payload_service::<EthEngineTypes>();
2205 let (to_engine, _engine_rx) = unbounded_channel();
2206 let api = EngineApi::new(
2207 provider.clone(),
2208 chain_spec.clone(),
2209 ConsensusEngineHandle::new(to_engine),
2210 payload_store.into(),
2211 NoopTransactionPool::default(),
2212 Runtime::test(),
2213 client,
2214 EngineCapabilities::default(),
2215 EthereumEngineValidator::new(chain_spec),
2216 false,
2217 NoopNetwork::default(),
2218 );
2219
2220 let mut block = Block::default();
2221 block.header.number = 1;
2222 let block_hash = block.header.hash_slow();
2223 provider.add_block(block_hash, block);
2224
2225 let mut block_without_bal = Block::default();
2226 block_without_bal.header.number = 3;
2227 let block_without_bal_hash = block_without_bal.header.hash_slow();
2228 provider.add_block(block_without_bal_hash, block_without_bal);
2229
2230 let raw_bal = Bytes::from_static(&[alloy_rlp::EMPTY_LIST_CODE]);
2231 bal_store.insert(NumHash::new(1, block_hash), RawBal::new(raw_bal.clone())).unwrap();
2232
2233 let response = api.get_payload_bodies_by_range_v2(1, 3).await.unwrap();
2234
2235 assert_eq!(response.len(), 3);
2236 assert_eq!(response[0].as_ref().unwrap().block_access_list, Some(raw_bal));
2237 assert!(response[1].is_none());
2238 assert_eq!(response[2].as_ref().unwrap().block_access_list, None);
2239 }
2240
2241 struct EngineApiTestHandle {
2242 #[allow(dead_code)]
2243 chain_spec: Arc<ChainSpec>,
2244 provider: Arc<MockEthProvider>,
2245 from_api: UnboundedReceiver<BeaconEngineMessage<EthEngineTypes>>,
2246 }
2247
2248 #[tokio::test]
2249 async fn forwards_responses_to_consensus_engine() {
2250 let (mut handle, api) = setup_engine_api();
2251
2252 tokio::spawn(async move {
2253 let payload_v1 = ExecutionPayloadV1::from_block_slow(&Block::default());
2254 let execution_data = ExecutionData {
2255 payload: payload_v1.into(),
2256 sidecar: ExecutionPayloadSidecar::none(),
2257 };
2258
2259 api.new_payload_v1(execution_data).await.unwrap();
2260 });
2261 assert_matches!(handle.from_api.recv().await, Some(BeaconEngineMessage::NewPayload { .. }));
2262 }
2263
2264 #[tokio::test]
2265 async fn new_payload_v5_accepts_amsterdam_payloads() {
2266 let chain_spec = Arc::new(ChainSpecBuilder::mainnet().amsterdam_activated().build());
2267 let provider = Arc::new(MockEthProvider::default());
2268 let payload_store = spawn_test_payload_service::<EthEngineTypes>();
2269 let (to_engine, mut engine_rx) = unbounded_channel();
2270
2271 let api = EngineApi::new(
2272 provider,
2273 chain_spec.clone(),
2274 ConsensusEngineHandle::new(to_engine),
2275 payload_store.into(),
2276 NoopTransactionPool::default(),
2277 Runtime::test(),
2278 ClientVersionV1 {
2279 code: ClientCode::RH,
2280 name: "Reth".to_string(),
2281 version: "v0.0.0-test".to_string(),
2282 commit: "test".to_string(),
2283 },
2284 EngineCapabilities::default(),
2285 EthereumEngineValidator::new(chain_spec),
2286 false,
2287 NoopNetwork::default(),
2288 );
2289
2290 tokio::spawn(async move {
2291 let payload_v1 = ExecutionPayloadV1::from_block_slow(&Block::default());
2292 let payload = ExecutionPayloadV4 {
2293 payload_inner: ExecutionPayloadV3 {
2294 payload_inner: ExecutionPayloadV2 {
2295 payload_inner: payload_v1,
2296 withdrawals: Vec::new(),
2297 },
2298 blob_gas_used: 0,
2299 excess_blob_gas: 0,
2300 },
2301 block_access_list: Bytes::from_static(b"bal"),
2302 slot_number: 1,
2303 };
2304 let execution_data = ExecutionData {
2305 payload: payload.into(),
2306 sidecar: ExecutionPayloadSidecar::v4(
2307 CancunPayloadFields {
2308 versioned_hashes: Vec::new(),
2309 parent_beacon_block_root: B256::ZERO,
2310 },
2311 PraguePayloadFields { requests: RequestsOrHash::Requests(Requests::default()) },
2312 ),
2313 };
2314
2315 api.new_payload_v5(execution_data).await.unwrap();
2316 });
2317
2318 assert_matches!(engine_rx.recv().await, Some(BeaconEngineMessage::NewPayload { .. }));
2319 }
2320
2321 fn setup_pre_amsterdam_engine_api() -> (
2323 EngineApiTestHandle,
2324 EngineApi<
2325 Arc<MockEthProvider>,
2326 EthEngineTypes,
2327 NoopTransactionPool,
2328 EthereumEngineValidator,
2329 ChainSpec,
2330 >,
2331 ) {
2332 let chain_spec = Arc::new(ChainSpecBuilder::mainnet().osaka_activated().build());
2333 assert!(!chain_spec.is_amsterdam_active_at_timestamp(0));
2334 let provider = Arc::new(MockEthProvider::default());
2335 let (to_engine, engine_rx) = unbounded_channel();
2336
2337 let api = EngineApi::new(
2338 provider.clone(),
2339 chain_spec.clone(),
2340 ConsensusEngineHandle::new(to_engine),
2341 spawn_test_payload_service::<EthEngineTypes>().into(),
2342 NoopTransactionPool::default(),
2343 Runtime::test(),
2344 ClientVersionV1 {
2345 code: ClientCode::RH,
2346 name: "Reth".to_string(),
2347 version: "v0.0.0-test".to_string(),
2348 commit: "test".to_string(),
2349 },
2350 EngineCapabilities::default(),
2351 EthereumEngineValidator::new(chain_spec.clone()),
2352 false,
2353 NoopNetwork::default(),
2354 );
2355 (EngineApiTestHandle { chain_spec, provider, from_api: engine_rx }, api)
2356 }
2357
2358 fn complete_amsterdam_execution_data() -> ExecutionData {
2360 let payload_v1 = ExecutionPayloadV1::from_block_slow(&Block::default());
2361 let payload = ExecutionPayloadV4 {
2362 payload_inner: ExecutionPayloadV3 {
2363 payload_inner: ExecutionPayloadV2 {
2364 payload_inner: payload_v1,
2365 withdrawals: Vec::new(),
2366 },
2367 blob_gas_used: 0,
2368 excess_blob_gas: 0,
2369 },
2370 block_access_list: Bytes::from_static(b"bal"),
2371 slot_number: 1,
2372 };
2373 ExecutionData {
2374 payload: payload.into(),
2375 sidecar: ExecutionPayloadSidecar::v4(
2376 CancunPayloadFields {
2377 versioned_hashes: Vec::new(),
2378 parent_beacon_block_root: B256::ZERO,
2379 },
2380 PraguePayloadFields { requests: RequestsOrHash::Requests(Requests::default()) },
2381 ),
2382 }
2383 }
2384
2385 #[tokio::test]
2386 async fn new_payload_v5_rejects_pre_amsterdam_payloads() {
2387 let (mut handle, api) = setup_pre_amsterdam_engine_api();
2388
2389 let err = api
2390 .new_payload_v5(complete_amsterdam_execution_data())
2391 .await
2392 .expect_err("pre-Amsterdam payload must be rejected");
2393 assert_matches!(
2394 err,
2395 EngineApiError::EngineObjectValidationError(
2396 EngineObjectValidationError::UnsupportedFork
2397 )
2398 );
2399 let err = ErrorObject::from(err);
2400 assert_eq!(err.code(), UNSUPPORTED_FORK_CODE);
2401
2402 assert!(handle.from_api.try_recv().is_err(), "rejected payload must not reach the engine");
2403 }
2404
2405 #[tokio::test]
2406 async fn new_payload_v6_rejects_pre_amsterdam_payloads() {
2407 let (mut handle, api) = setup_pre_amsterdam_engine_api();
2408
2409 let err = api
2410 .new_payload_v6(complete_amsterdam_execution_data())
2411 .await
2412 .expect_err("pre-Amsterdam payload must be rejected");
2413 assert_matches!(
2414 err,
2415 EngineApiError::EngineObjectValidationError(
2416 EngineObjectValidationError::UnsupportedFork
2417 )
2418 );
2419 let err = ErrorObject::from(err);
2420 assert_eq!(err.code(), UNSUPPORTED_FORK_CODE);
2421
2422 assert!(handle.from_api.try_recv().is_err(), "rejected payload must not reach the engine");
2423 }
2424
2425 #[tokio::test]
2426 async fn fcu_v4_rejects_pre_amsterdam_payload_attributes() {
2427 let (mut handle, api) = setup_pre_amsterdam_engine_api();
2428
2429 let state = ForkchoiceState {
2430 head_block_hash: B256::repeat_byte(0x55),
2431 safe_block_hash: B256::ZERO,
2432 finalized_block_hash: B256::ZERO,
2433 };
2434 let payload_attributes = PayloadAttributes {
2435 timestamp: 1,
2436 prev_randao: B256::ZERO,
2437 suggested_fee_recipient: Address::ZERO,
2438 withdrawals: Some(vec![]),
2439 parent_beacon_block_root: Some(B256::ZERO),
2440 slot_number: Some(1),
2441 ..Default::default()
2442 };
2443
2444 let api_task = tokio::spawn(async move {
2445 api.fork_choice_updated_v4(state, Some(payload_attributes), None).await
2446 });
2447
2448 let request =
2450 tokio::time::timeout(std::time::Duration::from_secs(1), handle.from_api.recv())
2451 .await
2452 .expect("timed out waiting for forkchoiceUpdated request")
2453 .expect("expected forkchoiceUpdated request");
2454 let response_tx = match request {
2455 BeaconEngineMessage::ForkchoiceUpdated { payload_attrs, tx, .. } => {
2456 assert!(payload_attrs.is_none());
2457 tx
2458 }
2459 other => panic!("unexpected engine message: {other:?}"),
2460 };
2461 response_tx
2462 .send(Ok(OnForkChoiceUpdated::valid(PayloadStatus::from_status(
2463 PayloadStatusEnum::Valid,
2464 ))))
2465 .expect("send valid response");
2466
2467 let err = api_task
2468 .await
2469 .expect("api task should not panic")
2470 .expect_err("pre-Amsterdam attributes must be rejected");
2471 assert_matches!(
2472 err,
2473 EngineApiError::EngineObjectValidationError(
2474 EngineObjectValidationError::UnsupportedFork
2475 )
2476 );
2477 let err = ErrorObject::from(err);
2478 assert_eq!(err.code(), UNSUPPORTED_FORK_CODE);
2479 }
2480
2481 #[derive(Clone)]
2482 struct TestNetworkInfo {
2483 syncing: bool,
2484 }
2485
2486 impl NetworkInfo for TestNetworkInfo {
2487 fn local_addr(&self) -> std::net::SocketAddr {
2488 (std::net::Ipv4Addr::UNSPECIFIED, 0).into()
2489 }
2490
2491 async fn network_status(&self) -> Result<NetworkStatus, NetworkError> {
2492 #[allow(deprecated)]
2493 Ok(NetworkStatus {
2494 client_version: "test".to_string(),
2495 protocol_version: 5,
2496 eth_protocol_info: EthProtocolInfo {
2497 network: 1,
2498 difficulty: None,
2499 genesis: Default::default(),
2500 config: Default::default(),
2501 head: Default::default(),
2502 },
2503 capabilities: vec![],
2504 })
2505 }
2506
2507 fn chain_id(&self) -> u64 {
2508 1
2509 }
2510
2511 fn cell_custody(&self) -> &CellCustody {
2512 static CELL_CUSTODY: std::sync::OnceLock<CellCustody> = std::sync::OnceLock::new();
2513 CELL_CUSTODY.get_or_init(CellCustody::default)
2514 }
2515
2516 fn is_syncing(&self) -> bool {
2517 self.syncing
2518 }
2519
2520 fn is_initially_syncing(&self) -> bool {
2521 self.syncing
2522 }
2523 }
2524
2525 #[tokio::test]
2526 async fn get_blobs_v3_returns_null_when_syncing() {
2527 let chain_spec: Arc<ChainSpec> =
2528 Arc::new(ChainSpecBuilder::mainnet().osaka_activated().build());
2529 let provider = Arc::new(MockEthProvider::default());
2530 let payload_store = spawn_test_payload_service::<EthEngineTypes>();
2531 let (to_engine, _engine_rx) = unbounded_channel::<BeaconEngineMessage<EthEngineTypes>>();
2532
2533 let api = EngineApi::new(
2534 provider,
2535 chain_spec.clone(),
2536 ConsensusEngineHandle::new(to_engine),
2537 payload_store.into(),
2538 NoopTransactionPool::default(),
2539 Runtime::test(),
2540 ClientVersionV1 {
2541 code: ClientCode::RH,
2542 name: "Reth".to_string(),
2543 version: "v0.0.0-test".to_string(),
2544 commit: "test".to_string(),
2545 },
2546 EngineCapabilities::default(),
2547 EthereumEngineValidator::new(chain_spec),
2548 false,
2549 TestNetworkInfo { syncing: true },
2550 );
2551
2552 let res = api.get_blobs_v3_metered(vec![B256::ZERO]);
2553 assert_matches!(res, Ok(None));
2554 }
2555
2556 #[tokio::test]
2557 async fn get_blobs_v4_returns_null_when_syncing() {
2558 let chain_spec: Arc<ChainSpec> =
2559 Arc::new(ChainSpecBuilder::mainnet().osaka_activated().build());
2560 let provider = Arc::new(MockEthProvider::default());
2561 let payload_store = spawn_test_payload_service::<EthEngineTypes>();
2562 let (to_engine, _engine_rx) = unbounded_channel::<BeaconEngineMessage<EthEngineTypes>>();
2563
2564 let api = EngineApi::new(
2565 provider,
2566 chain_spec.clone(),
2567 ConsensusEngineHandle::new(to_engine),
2568 payload_store.into(),
2569 NoopTransactionPool::default(),
2570 Runtime::test(),
2571 ClientVersionV1 {
2572 code: ClientCode::RH,
2573 name: "Reth".to_string(),
2574 version: "v0.0.0-test".to_string(),
2575 commit: "test".to_string(),
2576 },
2577 EngineCapabilities::default(),
2578 EthereumEngineValidator::new(chain_spec),
2579 false,
2580 TestNetworkInfo { syncing: true },
2581 );
2582
2583 let res = api.get_blobs_v4_metered(vec![B256::ZERO], B128::from(1u128.to_le_bytes()));
2584 assert_matches!(res, Ok(None));
2585 }
2586
2587 #[tokio::test]
2588 async fn fcu_v4_updates_shared_cell_custody_before_forkchoice_result() {
2589 let chain_spec: Arc<ChainSpec> =
2590 Arc::new(ChainSpecBuilder::mainnet().amsterdam_activated().build());
2591 let provider = Arc::new(MockEthProvider::default());
2592 let payload_store = spawn_test_payload_service::<EthEngineTypes>();
2593 let (to_engine, mut engine_rx) = unbounded_channel();
2594 let network = NoopNetwork::default();
2595 let cell_custody = network.cell_custody().clone();
2596
2597 let api = EngineApi::new(
2598 provider,
2599 chain_spec.clone(),
2600 ConsensusEngineHandle::new(to_engine),
2601 payload_store.into(),
2602 NoopTransactionPool::default(),
2603 Runtime::test(),
2604 ClientVersionV1 {
2605 code: ClientCode::RH,
2606 name: "Reth".to_string(),
2607 version: "v0.0.0-test".to_string(),
2608 commit: "test".to_string(),
2609 },
2610 EngineCapabilities::default(),
2611 EthereumEngineValidator::new(chain_spec),
2612 false,
2613 network,
2614 );
2615
2616 let state = ForkchoiceState {
2617 head_block_hash: B256::repeat_byte(0x33),
2618 safe_block_hash: B256::ZERO,
2619 finalized_block_hash: B256::ZERO,
2620 };
2621 let custody_columns = B128::from(0b1010u128.to_le_bytes());
2622 let expected_custody_columns = B128::from(0b1010u128);
2623
2624 let api_task = tokio::spawn(async move {
2625 api.fork_choice_updated_v4(state, None, Some(custody_columns)).await
2626 });
2627
2628 let request = tokio::time::timeout(std::time::Duration::from_secs(1), engine_rx.recv())
2629 .await
2630 .expect("timed out waiting for forkchoiceUpdated request")
2631 .expect("expected forkchoiceUpdated request");
2632 let response_tx = match request {
2633 BeaconEngineMessage::ForkchoiceUpdated { payload_attrs, tx, .. } => {
2634 assert!(payload_attrs.is_none());
2635 tx
2636 }
2637 other => panic!("unexpected engine message: {other:?}"),
2638 };
2639 assert_eq!(cell_custody.get(), expected_custody_columns);
2640
2641 response_tx
2642 .send(Ok(OnForkChoiceUpdated::valid(PayloadStatus::from_status(
2643 PayloadStatusEnum::Valid,
2644 ))))
2645 .expect("send valid response");
2646
2647 api_task
2648 .await
2649 .expect("api task should not panic")
2650 .expect("forkchoiceUpdatedV4 should succeed");
2651 assert_eq!(cell_custody.get(), expected_custody_columns);
2652 }
2653
2654 #[tokio::test]
2655 async fn fcu_v4_updates_shared_cell_custody_when_payload_attrs_invalid() {
2656 let chain_spec: Arc<ChainSpec> =
2657 Arc::new(ChainSpecBuilder::mainnet().amsterdam_activated().build());
2658 let provider = Arc::new(MockEthProvider::default());
2659 let payload_store = spawn_test_payload_service::<EthEngineTypes>();
2660 let (to_engine, mut engine_rx) = unbounded_channel();
2661 let network = NoopNetwork::default();
2662 let cell_custody = network.cell_custody().clone();
2663
2664 let api = EngineApi::new(
2665 provider,
2666 chain_spec.clone(),
2667 ConsensusEngineHandle::new(to_engine),
2668 payload_store.into(),
2669 NoopTransactionPool::default(),
2670 Runtime::test(),
2671 ClientVersionV1 {
2672 code: ClientCode::RH,
2673 name: "Reth".to_string(),
2674 version: "v0.0.0-test".to_string(),
2675 commit: "test".to_string(),
2676 },
2677 EngineCapabilities::default(),
2678 EthereumEngineValidator::new(chain_spec),
2679 false,
2680 network,
2681 );
2682
2683 let state = ForkchoiceState {
2684 head_block_hash: B256::repeat_byte(0x44),
2685 safe_block_hash: B256::ZERO,
2686 finalized_block_hash: B256::ZERO,
2687 };
2688 let payload_attributes = PayloadAttributes {
2689 timestamp: 1,
2690 prev_randao: B256::ZERO,
2691 suggested_fee_recipient: Address::ZERO,
2692 withdrawals: Some(vec![]),
2693 parent_beacon_block_root: None,
2694 slot_number: None,
2695 ..Default::default()
2696 };
2697 let custody_columns = B128::from(0b1010u128.to_le_bytes());
2698 let expected_custody_columns = B128::from(0b1010u128);
2699
2700 let api_task = tokio::spawn(async move {
2701 api.fork_choice_updated_v4(state, Some(payload_attributes), Some(custody_columns)).await
2702 });
2703
2704 let request = tokio::time::timeout(std::time::Duration::from_secs(1), engine_rx.recv())
2705 .await
2706 .expect("timed out waiting for forkchoiceUpdated request")
2707 .expect("expected forkchoiceUpdated request");
2708 let response_tx = match request {
2709 BeaconEngineMessage::ForkchoiceUpdated { payload_attrs, tx, .. } => {
2710 assert!(
2711 payload_attrs.is_none(),
2712 "when attrs are invalid, API should first evaluate forkchoice without attrs"
2713 );
2714 tx
2715 }
2716 other => panic!("unexpected engine message: {other:?}"),
2717 };
2718 assert_eq!(cell_custody.get(), expected_custody_columns);
2719
2720 response_tx
2721 .send(Ok(OnForkChoiceUpdated::valid(PayloadStatus::from_status(
2722 PayloadStatusEnum::Valid,
2723 ))))
2724 .expect("send valid response");
2725
2726 let response = api_task.await.expect("api task should not panic");
2727 assert_matches!(
2728 response,
2729 Err(EngineApiError::EngineObjectValidationError(
2730 reth_payload_primitives::EngineObjectValidationError::PayloadAttributes(_)
2731 ))
2732 );
2733 }
2734
2735 #[tokio::test]
2736 async fn fcu_v3_syncing_precedes_invalid_payload_attributes_validation() {
2737 let (mut handle, api) = setup_engine_api();
2738
2739 let state = ForkchoiceState {
2740 head_block_hash: B256::repeat_byte(0x11),
2741 safe_block_hash: B256::ZERO,
2742 finalized_block_hash: B256::ZERO,
2743 };
2744 let payload_attributes = PayloadAttributes {
2745 timestamp: 1,
2746 prev_randao: B256::ZERO,
2747 suggested_fee_recipient: Address::ZERO,
2748 withdrawals: Some(vec![]),
2749 parent_beacon_block_root: None,
2751 slot_number: None,
2752 ..Default::default()
2753 };
2754
2755 let api_task = tokio::spawn(async move {
2756 api.fork_choice_updated_v3(state, Some(payload_attributes)).await
2757 });
2758
2759 let request =
2760 tokio::time::timeout(std::time::Duration::from_secs(1), handle.from_api.recv())
2761 .await
2762 .expect("timed out waiting for forkchoiceUpdated request")
2763 .expect("expected forkchoiceUpdated request");
2764 let response_tx = match request {
2765 BeaconEngineMessage::ForkchoiceUpdated { payload_attrs, tx, .. } => {
2766 assert!(
2767 payload_attrs.is_none(),
2768 "FCU for syncing state should be evaluated before payload attributes"
2769 );
2770 tx
2771 }
2772 other => panic!("unexpected engine message: {other:?}"),
2773 };
2774
2775 response_tx.send(Ok(OnForkChoiceUpdated::syncing())).expect("send syncing response");
2776
2777 let response = api_task
2778 .await
2779 .expect("api task should not panic")
2780 .expect("forkchoiceUpdatedV3 should return a syncing response");
2781 assert!(response.payload_status.is_syncing());
2782 assert!(response.payload_id.is_none());
2783 }
2784
2785 #[tokio::test]
2786 async fn fcu_v3_valid_forkchoice_missing_beacon_root_returns_invalid_attributes() {
2787 let (mut handle, api) = setup_engine_api();
2788
2789 let state = ForkchoiceState {
2790 head_block_hash: B256::repeat_byte(0x22),
2791 safe_block_hash: B256::ZERO,
2792 finalized_block_hash: B256::ZERO,
2793 };
2794 let payload_attributes = PayloadAttributes {
2795 timestamp: 1,
2796 prev_randao: B256::ZERO,
2797 suggested_fee_recipient: Address::ZERO,
2798 withdrawals: Some(vec![]),
2799 parent_beacon_block_root: None,
2800 slot_number: None,
2801 ..Default::default()
2802 };
2803
2804 let api_task = tokio::spawn(async move {
2805 api.fork_choice_updated_v3(state, Some(payload_attributes)).await
2806 });
2807
2808 let request =
2809 tokio::time::timeout(std::time::Duration::from_secs(1), handle.from_api.recv())
2810 .await
2811 .expect("timed out waiting for forkchoiceUpdated request")
2812 .expect("expected forkchoiceUpdated request");
2813
2814 let response_tx = match request {
2815 BeaconEngineMessage::ForkchoiceUpdated { payload_attrs, tx, .. } => {
2816 assert!(
2817 payload_attrs.is_none(),
2818 "when attrs are invalid, API should first evaluate forkchoice without attrs"
2819 );
2820 tx
2821 }
2822 other => panic!("unexpected engine message: {other:?}"),
2823 };
2824
2825 response_tx
2826 .send(Ok(OnForkChoiceUpdated::valid(PayloadStatus::from_status(
2827 PayloadStatusEnum::Valid,
2828 ))))
2829 .expect("send valid response");
2830
2831 let response = api_task.await.expect("api task should not panic");
2832 assert_matches!(
2833 response,
2834 Err(EngineApiError::EngineObjectValidationError(
2835 reth_payload_primitives::EngineObjectValidationError::PayloadAttributes(_)
2836 ))
2837 );
2838
2839 match tokio::time::timeout(std::time::Duration::from_millis(100), handle.from_api.recv())
2840 .await
2841 {
2842 Err(_) | Ok(None) => {}
2843 Ok(Some(BeaconEngineMessage::ForkchoiceUpdated { .. })) => {
2844 panic!("no second forkchoiceUpdated call should be sent when attrs are invalid")
2845 }
2846 Ok(Some(other)) => panic!("unexpected engine message: {other:?}"),
2847 }
2848 }
2849
2850 mod get_payload_bodies {
2852 use super::*;
2853 use alloy_rpc_types_engine::ExecutionPayloadBodyV1;
2854 use reth_testing_utils::generators::{self, random_block_range, BlockRangeParams};
2855
2856 #[tokio::test]
2857 async fn invalid_params() {
2858 let (_, api) = setup_engine_api();
2859
2860 let by_range_tests = [
2861 (0, 0),
2863 (0, 1),
2864 (1, 0),
2865 ];
2866
2867 for (start, count) in by_range_tests {
2869 let res = api.get_payload_bodies_by_range_v1(start, count).await;
2870 assert_matches!(res, Err(EngineApiError::InvalidBodiesRange { .. }));
2871 }
2872 }
2873
2874 #[tokio::test]
2875 async fn request_too_large() {
2876 let (_, api) = setup_engine_api();
2877
2878 let request_count = MAX_PAYLOAD_BODIES_LIMIT + 1;
2879 let res = api.get_payload_bodies_by_range_v1(0, request_count).await;
2880 assert_matches!(res, Err(EngineApiError::PayloadRequestTooLarge { .. }));
2881 }
2882
2883 #[tokio::test]
2884 async fn returns_payload_bodies() {
2885 let mut rng = generators::rng();
2886 let (handle, api) = setup_engine_api();
2887
2888 let (start, count) = (1, 10);
2889 let blocks = random_block_range(
2890 &mut rng,
2891 start..=start + count - 1,
2892 BlockRangeParams { tx_count: 0..2, ..Default::default() },
2893 );
2894 handle
2895 .provider
2896 .extend_blocks(blocks.iter().cloned().map(|b| (b.hash(), b.into_block())));
2897
2898 let expected = blocks
2899 .iter()
2900 .cloned()
2901 .map(|b| Some(ExecutionPayloadBodyV1::from_block(b.into_block())))
2902 .collect::<Vec<_>>();
2903
2904 let res = api.get_payload_bodies_by_range_v1(start, count).await.unwrap();
2905 assert_eq!(res, expected);
2906 }
2907
2908 #[tokio::test]
2909 async fn returns_payload_bodies_with_gaps() {
2910 let mut rng = generators::rng();
2911 let (handle, api) = setup_engine_api();
2912
2913 let (start, count) = (1, 100);
2914 let blocks = random_block_range(
2915 &mut rng,
2916 start..=start + count - 1,
2917 BlockRangeParams { tx_count: 0..2, ..Default::default() },
2918 );
2919
2920 let first_missing_range = 26..=50;
2922 let second_missing_range = 76..=100;
2923 handle.provider.extend_blocks(
2924 blocks
2925 .iter()
2926 .filter(|b| {
2927 !first_missing_range.contains(&b.number) &&
2928 !second_missing_range.contains(&b.number)
2929 })
2930 .map(|b| (b.hash(), b.clone().into_block())),
2931 );
2932
2933 let expected = blocks
2934 .iter()
2935 .filter(|b| !second_missing_range.contains(&b.number))
2938 .cloned()
2939 .map(|b| {
2940 if first_missing_range.contains(&b.number) {
2941 None
2942 } else {
2943 Some(ExecutionPayloadBodyV1::from_block(b.into_block()))
2944 }
2945 })
2946 .collect::<Vec<_>>();
2947
2948 let res = api.get_payload_bodies_by_range_v1(start, count).await.unwrap();
2949 assert_eq!(res, expected);
2950
2951 let expected = blocks
2952 .iter()
2953 .cloned()
2954 .map(|b| {
2957 if first_missing_range.contains(&b.number) ||
2958 second_missing_range.contains(&b.number)
2959 {
2960 None
2961 } else {
2962 Some(ExecutionPayloadBodyV1::from_block(b.into_block()))
2963 }
2964 })
2965 .collect::<Vec<_>>();
2966
2967 let hashes = blocks.iter().map(|b| b.hash()).collect();
2968 let res = api.get_payload_bodies_by_hash_v1(hashes).await.unwrap();
2969 assert_eq!(res, expected);
2970 }
2971 }
2972
2973 #[tokio::test]
2974 async fn new_payload_rejects_pre_shanghai_timestamps() {
2975 let (mut handle, api) = setup_engine_api();
2976 assert!(!handle.chain_spec.is_shanghai_active_at_timestamp(0));
2977
2978 let errors = tokio::time::timeout(Duration::from_secs(1), async {
2979 [
2980 api.new_payload_v5(complete_amsterdam_execution_data()).await.unwrap_err(),
2981 api.new_payload_v6(complete_amsterdam_execution_data()).await.unwrap_err(),
2982 ]
2983 })
2984 .await
2985 .expect("payload validation must complete without reaching the engine");
2986
2987 for error in errors {
2988 assert_matches!(
2989 error,
2990 EngineApiError::EngineObjectValidationError(
2991 EngineObjectValidationError::UnsupportedFork
2992 )
2993 );
2994 assert_eq!(ErrorObject::from(error).code(), UNSUPPORTED_FORK_CODE);
2995 }
2996 assert_matches!(handle.from_api.try_recv(), Err(TryRecvError::Empty));
2997 }
2998
2999 #[tokio::test]
3000 async fn fcu_rejects_pre_shanghai_timestamps() {
3001 for version in [EngineApiMessageVersion::V4, EngineApiMessageVersion::V5] {
3002 let (mut handle, api) = setup_engine_api();
3003 assert!(!handle.chain_spec.is_shanghai_active_at_timestamp(1));
3004 let state = ForkchoiceState::same_hash(B256::repeat_byte(0x55));
3005 let attributes = PayloadAttributes {
3006 timestamp: 1,
3007 withdrawals: Some(vec![]),
3008 parent_beacon_block_root: Some(B256::ZERO),
3009 slot_number: Some(1),
3010 target_gas_limit: Some(30_000_000),
3011 ..Default::default()
3012 };
3013 let api_task = tokio::spawn(async move {
3014 match version {
3015 EngineApiMessageVersion::V4 => {
3016 api.fork_choice_updated_v4(state, Some(attributes), None).await.map(|_| ())
3017 }
3018 EngineApiMessageVersion::V5 => {
3019 api.fork_choice_updated_v5(state, Some(attributes), None).await.map(|_| ())
3020 }
3021 _ => unreachable!(),
3022 }
3023 });
3024
3025 let request = tokio::time::timeout(Duration::from_secs(1), handle.from_api.recv())
3026 .await
3027 .expect("timed out waiting for forkchoiceUpdated request")
3028 .expect("expected forkchoiceUpdated request");
3029 let BeaconEngineMessage::ForkchoiceUpdated { payload_attrs, tx, .. } = request else {
3030 panic!("unexpected engine message: {request:?}");
3031 };
3032 assert!(payload_attrs.is_none());
3033 tx.send(Ok(OnForkChoiceUpdated::valid(PayloadStatus::from_status(
3034 PayloadStatusEnum::Valid,
3035 ))))
3036 .expect("send valid response");
3037
3038 let error = tokio::time::timeout(Duration::from_secs(1), api_task)
3039 .await
3040 .expect("forkchoiceUpdated must complete after the engine response")
3041 .expect("api task should not panic")
3042 .unwrap_err();
3043 assert_matches!(
3044 error,
3045 EngineApiError::EngineObjectValidationError(
3046 EngineObjectValidationError::UnsupportedFork
3047 )
3048 );
3049 assert_eq!(ErrorObject::from(error).code(), UNSUPPORTED_FORK_CODE);
3050 }
3051 }
3052}