1use crate::{
2 error::BeaconForkChoiceUpdateError, BeaconOnNewPayloadError, ExecutionPayload, ForkchoiceStatus,
3};
4use alloy_eips::eip4895::Withdrawal;
5use alloy_primitives::{Bytes, B256};
6use alloy_rpc_types_engine::{
7 ExecutionData, ForkchoiceState, ForkchoiceUpdateError, ForkchoiceUpdated, PayloadId,
8 PayloadStatus, PayloadStatusEnum,
9};
10use core::{
11 fmt::{self, Display},
12 future::Future,
13 pin::Pin,
14 task::{ready, Context, Poll},
15};
16use futures::{future::Either, FutureExt, TryFutureExt};
17use reth_errors::RethResult;
18use reth_payload_builder_primitives::PayloadBuilderError;
19use reth_payload_primitives::PayloadTypes;
20use std::time::{Duration, Instant};
21use tokio::sync::{mpsc::UnboundedSender, oneshot};
22use tracing::Span;
23
24#[deprecated(note = "Use ConsensusEngineHandle instead")]
26pub type BeaconConsensusEngineHandle<Payload> = ConsensusEngineHandle<Payload>;
27
28pub type OnForkChoiceUpdatedResult = Result<ForkchoiceUpdated, BeaconForkChoiceUpdateError>;
30
31#[must_use = "futures do nothing unless you `.await` or poll them"]
35#[derive(Debug)]
36pub struct OnForkChoiceUpdated {
37 forkchoice_status: ForkchoiceStatus,
42 fut: Either<futures::future::Ready<OnForkChoiceUpdatedResult>, PendingPayloadId>,
44}
45
46impl OnForkChoiceUpdated {
49 pub const fn forkchoice_status(&self) -> ForkchoiceStatus {
51 self.forkchoice_status
52 }
53
54 pub fn syncing() -> Self {
56 let status = PayloadStatus::from_status(PayloadStatusEnum::Syncing);
57 Self {
58 forkchoice_status: ForkchoiceStatus::from_payload_status(&status.status),
59 fut: Either::Left(futures::future::ready(Ok(ForkchoiceUpdated::new(status)))),
60 }
61 }
62
63 pub fn valid(status: PayloadStatus) -> Self {
66 Self {
67 forkchoice_status: ForkchoiceStatus::from_payload_status(&status.status),
68 fut: Either::Left(futures::future::ready(Ok(ForkchoiceUpdated::new(status)))),
69 }
70 }
71
72 pub fn with_invalid(status: PayloadStatus) -> Self {
75 Self {
76 forkchoice_status: ForkchoiceStatus::from_payload_status(&status.status),
77 fut: Either::Left(futures::future::ready(Ok(ForkchoiceUpdated::new(status)))),
78 }
79 }
80
81 pub fn invalid_state() -> Self {
84 Self {
85 forkchoice_status: ForkchoiceStatus::Invalid,
86 fut: Either::Left(futures::future::ready(Err(
87 ForkchoiceUpdateError::InvalidState.into()
88 ))),
89 }
90 }
91
92 pub fn too_deep_reorg() -> Self {
95 Self {
96 forkchoice_status: ForkchoiceStatus::Invalid,
97 fut: Either::Left(futures::future::ready(Err(
98 ForkchoiceUpdateError::TooDeepReorg.into()
99 ))),
100 }
101 }
102
103 pub fn invalid_payload_attributes() -> Self {
106 Self {
107 forkchoice_status: ForkchoiceStatus::Valid,
109 fut: Either::Left(futures::future::ready(Err(
110 ForkchoiceUpdateError::UpdatedInvalidPayloadAttributes.into(),
111 ))),
112 }
113 }
114
115 pub const fn updated_with_pending_payload_id(
119 payload_status: PayloadStatus,
120 pending_payload_id: oneshot::Receiver<Result<PayloadId, PayloadBuilderError>>,
121 ) -> Self {
122 Self {
123 forkchoice_status: ForkchoiceStatus::from_payload_status(&payload_status.status),
124 fut: Either::Right(PendingPayloadId {
125 payload_status: Some(payload_status),
126 pending_payload_id,
127 }),
128 }
129 }
130}
131
132impl Future for OnForkChoiceUpdated {
133 type Output = OnForkChoiceUpdatedResult;
134
135 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
136 self.get_mut().fut.poll_unpin(cx)
137 }
138}
139
140#[derive(Debug)]
143struct PendingPayloadId {
144 payload_status: Option<PayloadStatus>,
145 pending_payload_id: oneshot::Receiver<Result<PayloadId, PayloadBuilderError>>,
146}
147
148impl Future for PendingPayloadId {
149 type Output = OnForkChoiceUpdatedResult;
150
151 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
152 let this = self.get_mut();
153 let payload_id = ready!(this.pending_payload_id.poll_unpin(cx))
154 .unwrap_or_else(|err| Err(err.into()))
155 .map_err(BeaconForkChoiceUpdateError::PayloadJob)?;
156 Poll::Ready(Ok(ForkchoiceUpdated {
157 payload_status: this.payload_status.take().expect("Polled after completion"),
158 payload_id: Some(payload_id),
159 }))
160 }
161}
162
163#[derive(Debug, Clone, Copy)]
165pub struct NewPayloadTimings {
166 pub latency: Duration,
168 pub persistence_wait: Duration,
172 pub execution_cache_wait: Option<Duration>,
176 pub sparse_trie_wait: Option<Duration>,
180}
181
182#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
187pub struct BigBlockData<ExecutionData> {
188 pub env_switches: Vec<ExecutionData>,
194 pub prior_block_hashes: Vec<(u64, alloy_primitives::B256)>,
198 pub block_number: u64,
200 #[serde(default, skip_serializing_if = "Option::is_none")]
202 pub merged_block_access_list: Option<Bytes>,
203}
204
205impl ExecutionPayload for BigBlockData<ExecutionData> {
206 fn parent_hash(&self) -> B256 {
207 self.env_switches[0].parent_hash()
208 }
209
210 fn block_hash(&self) -> B256 {
211 self.env_switches.last().unwrap().block_hash()
212 }
213
214 fn block_number(&self) -> u64 {
215 self.block_number
216 }
217
218 fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
219 self.env_switches[0].withdrawals()
220 }
221
222 fn block_access_list(&self) -> Option<&Bytes> {
223 self.merged_block_access_list.as_ref()
224 }
225
226 fn parent_beacon_block_root(&self) -> Option<B256> {
227 self.env_switches[0].parent_beacon_block_root()
228 }
229
230 fn timestamp(&self) -> u64 {
231 self.env_switches[0].timestamp()
232 }
233
234 fn gas_used(&self) -> u64 {
235 self.env_switches.iter().map(|data| data.gas_used()).sum()
236 }
237
238 fn gas_limit(&self) -> u64 {
239 self.env_switches.iter().map(|data| data.gas_limit()).sum()
240 }
241
242 fn transaction_count(&self) -> usize {
243 self.env_switches.iter().map(|data| data.transaction_count()).sum()
244 }
245
246 fn slot_number(&self) -> Option<u64> {
247 self.env_switches[0].payload.slot_number()
248 }
249}
250
251#[derive(Debug)]
258pub enum BeaconEngineMessage<Payload: PayloadTypes> {
259 NewPayload {
261 cause: Span,
263 payload: Payload::ExecutionData,
265 tx: oneshot::Sender<Result<PayloadStatus, BeaconOnNewPayloadError>>,
267 },
268 RethNewPayload {
275 cause: Span,
277 payload: Payload::ExecutionData,
279 wait_for_persistence: bool,
281 wait_for_caches: bool,
283 tx: oneshot::Sender<Result<(PayloadStatus, NewPayloadTimings), BeaconOnNewPayloadError>>,
285 enqueued_at: Instant,
287 },
288 ForkchoiceUpdated {
290 cause: Span,
292 state: ForkchoiceState,
294 payload_attrs: Option<Payload::PayloadAttributes>,
296 tx: oneshot::Sender<RethResult<OnForkChoiceUpdated>>,
298 },
299}
300
301impl<Payload: PayloadTypes> Display for BeaconEngineMessage<Payload> {
302 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303 match self {
304 Self::NewPayload { payload, .. } => {
305 write!(
306 f,
307 "NewPayload(parent: {}, number: {}, hash: {})",
308 payload.parent_hash(),
309 payload.block_number(),
310 payload.block_hash()
311 )
312 }
313 Self::RethNewPayload { payload, .. } => {
314 write!(
315 f,
316 "RethNewPayload(parent: {}, number: {}, hash: {})",
317 payload.parent_hash(),
318 payload.block_number(),
319 payload.block_hash()
320 )
321 }
322 Self::ForkchoiceUpdated { state, payload_attrs, .. } => {
323 write!(
326 f,
327 "ForkchoiceUpdated {{ state: {state:?}, has_payload_attributes: {} }}",
328 payload_attrs.is_some()
329 )
330 }
331 }
332 }
333}
334
335#[derive(Debug, Clone)]
339pub struct ConsensusEngineHandle<Payload>
340where
341 Payload: PayloadTypes,
342{
343 to_engine: UnboundedSender<BeaconEngineMessage<Payload>>,
344}
345
346impl<Payload> ConsensusEngineHandle<Payload>
347where
348 Payload: PayloadTypes,
349{
350 pub const fn new(to_engine: UnboundedSender<BeaconEngineMessage<Payload>>) -> Self {
352 Self { to_engine }
353 }
354
355 pub async fn new_payload(
359 &self,
360 payload: Payload::ExecutionData,
361 ) -> Result<PayloadStatus, BeaconOnNewPayloadError> {
362 let (tx, rx) = oneshot::channel();
363 let _ = self.to_engine.send(BeaconEngineMessage::NewPayload {
364 cause: Span::current(),
365 payload,
366 tx,
367 });
368 rx.await.map_err(|_| BeaconOnNewPayloadError::EngineUnavailable)?
369 }
370
371 pub async fn reth_new_payload(
379 &self,
380 payload: Payload::ExecutionData,
381 wait_for_persistence: bool,
382 wait_for_caches: bool,
383 ) -> Result<(PayloadStatus, NewPayloadTimings), BeaconOnNewPayloadError> {
384 let (tx, rx) = oneshot::channel();
385 let _ = self.to_engine.send(BeaconEngineMessage::RethNewPayload {
386 cause: Span::current(),
387 payload,
388 wait_for_persistence,
389 wait_for_caches,
390 tx,
391 enqueued_at: Instant::now(),
392 });
393 rx.await.map_err(|_| BeaconOnNewPayloadError::EngineUnavailable)?
394 }
395
396 pub async fn fork_choice_updated(
400 &self,
401 state: ForkchoiceState,
402 payload_attrs: Option<Payload::PayloadAttributes>,
403 ) -> Result<ForkchoiceUpdated, BeaconForkChoiceUpdateError> {
404 self.send_fork_choice_updated(state, payload_attrs)
405 .map_err(|_| BeaconForkChoiceUpdateError::EngineUnavailable)
406 .await?
407 .map_err(BeaconForkChoiceUpdateError::internal)?
408 .await
409 }
410
411 fn send_fork_choice_updated(
414 &self,
415 state: ForkchoiceState,
416 payload_attrs: Option<Payload::PayloadAttributes>,
417 ) -> oneshot::Receiver<RethResult<OnForkChoiceUpdated>> {
418 let (tx, rx) = oneshot::channel();
419 let _ = self.to_engine.send(BeaconEngineMessage::ForkchoiceUpdated {
420 cause: Span::current(),
421 state,
422 payload_attrs,
423 tx,
424 });
425 rx
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432
433 fn on_payload_job(
436 result: Option<Result<PayloadId, PayloadBuilderError>>,
437 ) -> OnForkChoiceUpdatedResult {
438 let (tx, rx) = oneshot::channel();
439 match result {
440 Some(result) => tx.send(result).unwrap(),
441 None => drop(tx),
442 }
443 OnForkChoiceUpdated::updated_with_pending_payload_id(
444 PayloadStatus::from_status(PayloadStatusEnum::Valid),
445 rx,
446 )
447 .now_or_never()
448 .expect("payload job result is ready")
449 }
450
451 #[test]
452 fn payload_job_start_errors_are_returned() {
453 let id = PayloadId::new([1; 8]);
454 assert_eq!(on_payload_job(Some(Ok(id))).unwrap().payload_id, Some(id));
455
456 let err = on_payload_job(Some(Err(PayloadBuilderError::MissingParentHeader(B256::ZERO))))
457 .unwrap_err();
458 assert!(matches!(
459 err,
460 BeaconForkChoiceUpdateError::PayloadJob(PayloadBuilderError::MissingParentHeader(_))
461 ));
462
463 let err = on_payload_job(None).unwrap_err();
464 assert!(matches!(
465 err,
466 BeaconForkChoiceUpdateError::PayloadJob(PayloadBuilderError::ChannelClosed)
467 ));
468 }
469}