Skip to main content

reth_engine_tree/
engine.rs

1//! An engine API handler for the chain.
2
3use crate::{
4    backfill::BackfillAction,
5    chain::{ChainHandler, FromOrchestrator, HandlerEvent},
6    download::{BlockDownloader, DownloadAction, DownloadOutcome},
7};
8use alloy_primitives::{map::B256Set, B256};
9use crossbeam_channel::Sender;
10use futures::{Stream, StreamExt};
11use reth_engine_primitives::{BeaconEngineMessage, ConsensusEngineEvent};
12use reth_ethereum_primitives::EthPrimitives;
13use reth_network_p2p::full_block::SealedBlockWithAccessList;
14use reth_payload_primitives::{BuiltPayloadExecutedBlock, PayloadTypes};
15use reth_primitives_traits::{Block, NodePrimitives};
16use std::{
17    fmt::Display,
18    task::{ready, Context, Poll},
19};
20use tokio::sync::mpsc::UnboundedReceiver;
21
22/// A [`ChainHandler`] that advances the chain based on incoming requests (CL engine API).
23///
24/// This is a general purpose request handler with network access.
25/// This type listens for incoming messages and processes them via the configured request handler.
26///
27/// ## Overview
28///
29/// This type is an orchestrator for incoming messages and responsible for delegating requests
30/// received from the CL to the handler.
31///
32/// It is responsible for handling the following:
33/// - Delegating incoming requests to the [`EngineRequestHandler`].
34/// - Advancing the [`EngineRequestHandler`] by polling it and emitting events.
35/// - Downloading blocks on demand from the network if requested by the [`EngineApiRequestHandler`].
36///
37/// The core logic is part of the [`EngineRequestHandler`], which is responsible for processing the
38/// incoming requests.
39#[derive(Debug)]
40pub struct EngineHandler<T, S, D> {
41    /// Processes requests.
42    ///
43    /// This type is responsible for processing incoming requests.
44    handler: T,
45    /// Receiver for incoming requests (from the engine API endpoint) that need to be processed.
46    incoming_requests: S,
47    /// A downloader to download blocks on demand.
48    downloader: D,
49}
50
51impl<T, S, D> EngineHandler<T, S, D> {
52    /// Creates a new [`EngineHandler`] with the given handler and downloader and incoming stream of
53    /// requests.
54    pub const fn new(handler: T, downloader: D, incoming_requests: S) -> Self
55    where
56        T: EngineRequestHandler,
57    {
58        Self { handler, incoming_requests, downloader }
59    }
60
61    /// Returns a mutable reference to the request handler.
62    pub const fn handler_mut(&mut self) -> &mut T {
63        &mut self.handler
64    }
65}
66
67impl<T, S, D> ChainHandler for EngineHandler<T, S, D>
68where
69    T: EngineRequestHandler<Block = D::Block>,
70    S: Stream + Send + Sync + Unpin + 'static,
71    <S as Stream>::Item: Into<T::Request>,
72    D: BlockDownloader,
73{
74    type Event = T::Event;
75
76    fn on_event(&mut self, event: FromOrchestrator) {
77        // delegate event to the handler
78        self.handler.on_event(event.into());
79    }
80
81    fn poll(&mut self, cx: &mut Context<'_>) -> Poll<HandlerEvent<Self::Event>> {
82        loop {
83            // drain the handler first
84            while let Poll::Ready(ev) = self.handler.poll(cx) {
85                match ev {
86                    RequestHandlerEvent::HandlerEvent(ev) => {
87                        return match ev {
88                            HandlerEvent::BackfillAction(target) => {
89                                // bubble up backfill sync request
90                                // Only a new run, not a target update, makes downloads stale.
91                                if matches!(target, BackfillAction::Start(_)) {
92                                    self.downloader.on_action(DownloadAction::Clear);
93                                }
94                                Poll::Ready(HandlerEvent::BackfillAction(target))
95                            }
96                            HandlerEvent::Event(ev) => {
97                                // bubble up the event
98                                Poll::Ready(HandlerEvent::Event(ev))
99                            }
100                            HandlerEvent::FatalError => Poll::Ready(HandlerEvent::FatalError),
101                        }
102                    }
103                    RequestHandlerEvent::Download(req) => {
104                        // delegate download request to the downloader
105                        self.downloader.on_action(DownloadAction::Download(req));
106                    }
107                }
108            }
109
110            // pop the next incoming request
111            if let Poll::Ready(Some(req)) = self.incoming_requests.poll_next_unpin(cx) {
112                // and delegate the request to the handler
113                self.handler.on_event(FromEngine::Request(req.into()));
114                // skip downloading in this iteration to allow the handler to process the request
115                continue
116            }
117
118            // advance the downloader
119            if let Poll::Ready(outcome) = self.downloader.poll(cx) {
120                if let DownloadOutcome::Blocks(blocks) = outcome {
121                    // delegate the downloaded blocks to the handler
122                    self.handler.on_event(FromEngine::DownloadedBlocks(blocks));
123                }
124                continue
125            }
126
127            return Poll::Pending
128        }
129    }
130}
131
132/// A type that processes incoming requests (e.g. requests from the consensus layer, engine API,
133/// such as newPayload).
134///
135/// ## Control flow
136///
137/// Requests and certain updates, such as a change in backfill sync status, are delegated to this
138/// type via [`EngineRequestHandler::on_event`]. This type is responsible for processing the
139/// incoming requests and advancing the chain and emit events when it is polled.
140pub trait EngineRequestHandler: Send + Sync {
141    /// Event type this handler can emit
142    type Event: Send;
143    /// The request type this handler can process.
144    type Request;
145    /// Type of the block sent in [`FromEngine::DownloadedBlocks`] variant.
146    type Block: Block;
147
148    /// Informs the handler about an event from the [`EngineHandler`].
149    fn on_event(&mut self, event: FromEngine<Self::Request, Self::Block>);
150
151    /// Advances the handler.
152    fn poll(&mut self, cx: &mut Context<'_>) -> Poll<RequestHandlerEvent<Self::Event>>;
153}
154
155/// An [`EngineRequestHandler`] that processes engine API requests by delegating to an execution
156/// task.
157///
158/// This type is responsible for advancing the chain during live sync (following the tip of the
159/// chain).
160///
161/// It advances the chain based on received engine API requests by delegating them to the tree
162/// executor.
163///
164/// There are two types of requests that can be processed:
165///
166/// - `on_new_payload`: Executes the payload and inserts it into the tree. These are allowed to be
167///   processed concurrently.
168/// - `on_forkchoice_updated`: Updates the fork choice based on the new head. These require write
169///   access to the database and are skipped if the handler can't acquire exclusive access to the
170///   database.
171///
172/// In case required blocks are missing, the handler will request them from the network, by emitting
173/// a download request upstream.
174#[derive(Debug)]
175pub struct EngineApiRequestHandler<Request, N: NodePrimitives> {
176    /// channel to send messages to the tree to execute the payload.
177    to_tree: Sender<FromEngine<Request, N::Block>>,
178    /// channel to receive messages from the tree.
179    from_tree: UnboundedReceiver<EngineApiEvent<N>>,
180}
181
182impl<Request, N: NodePrimitives> EngineApiRequestHandler<Request, N> {
183    /// Creates a new `EngineApiRequestHandler`.
184    pub const fn new(
185        to_tree: Sender<FromEngine<Request, N::Block>>,
186        from_tree: UnboundedReceiver<EngineApiEvent<N>>,
187    ) -> Self {
188        Self { to_tree, from_tree }
189    }
190}
191
192impl<Request, N: NodePrimitives> EngineRequestHandler for EngineApiRequestHandler<Request, N>
193where
194    Request: Send,
195{
196    type Event = ConsensusEngineEvent<N>;
197    type Request = Request;
198    type Block = N::Block;
199
200    fn on_event(&mut self, event: FromEngine<Self::Request, Self::Block>) {
201        // delegate to the tree
202        let _ = self.to_tree.send(event);
203    }
204
205    fn poll(&mut self, cx: &mut Context<'_>) -> Poll<RequestHandlerEvent<Self::Event>> {
206        let Some(ev) = ready!(self.from_tree.poll_recv(cx)) else {
207            return Poll::Ready(RequestHandlerEvent::HandlerEvent(HandlerEvent::FatalError))
208        };
209
210        let ev = match ev {
211            EngineApiEvent::BeaconConsensus(ev) => {
212                RequestHandlerEvent::HandlerEvent(HandlerEvent::Event(ev))
213            }
214            EngineApiEvent::BackfillAction(action) => {
215                RequestHandlerEvent::HandlerEvent(HandlerEvent::BackfillAction(action))
216            }
217            EngineApiEvent::Download(action) => RequestHandlerEvent::Download(action),
218        };
219        Poll::Ready(ev)
220    }
221}
222
223/// The type for specifying the kind of engine api.
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
225pub enum EngineApiKind {
226    /// The chain contains Ethereum configuration.
227    #[default]
228    Ethereum,
229    /// The chain contains Optimism configuration.
230    OpStack,
231}
232
233impl EngineApiKind {
234    /// Returns true if this is the ethereum variant
235    pub const fn is_ethereum(&self) -> bool {
236        matches!(self, Self::Ethereum)
237    }
238
239    /// Returns true if this is the opstack variant
240    pub const fn is_opstack(&self) -> bool {
241        matches!(self, Self::OpStack)
242    }
243}
244
245/// The request variants that the engine API handler can receive.
246#[derive(Debug)]
247pub enum EngineApiRequest<T: PayloadTypes, N: NodePrimitives> {
248    /// A request received from the consensus engine.
249    Beacon(BeaconEngineMessage<T>),
250    /// Request to insert an already executed block, e.g. via payload building.
251    InsertExecutedBlock(BuiltPayloadExecutedBlock<N>),
252}
253
254impl<T: PayloadTypes, N: NodePrimitives> Display for EngineApiRequest<T, N> {
255    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256        match self {
257            Self::Beacon(msg) => msg.fmt(f),
258            Self::InsertExecutedBlock(payload) => {
259                write!(f, "InsertExecutedBlock({:?})", payload.recovered_block.num_hash())
260            }
261        }
262    }
263}
264
265impl<T: PayloadTypes, N: NodePrimitives> From<BeaconEngineMessage<T>> for EngineApiRequest<T, N> {
266    fn from(msg: BeaconEngineMessage<T>) -> Self {
267        Self::Beacon(msg)
268    }
269}
270
271impl<T: PayloadTypes, N: NodePrimitives> From<EngineApiRequest<T, N>>
272    for FromEngine<EngineApiRequest<T, N>, N::Block>
273{
274    fn from(req: EngineApiRequest<T, N>) -> Self {
275        Self::Request(req)
276    }
277}
278
279/// Events emitted by the engine API handler.
280#[derive(Debug)]
281pub enum EngineApiEvent<N: NodePrimitives = EthPrimitives> {
282    /// Event from the consensus engine.
283    // TODO(mattsse): find a more appropriate name for this variant, consider phasing it out.
284    BeaconConsensus(ConsensusEngineEvent<N>),
285    /// Backfill action is needed.
286    BackfillAction(BackfillAction),
287    /// Block download is needed.
288    Download(DownloadRequest),
289}
290
291impl<N: NodePrimitives> EngineApiEvent<N> {
292    /// Returns `true` if the event is a backfill action.
293    pub const fn is_backfill_action(&self) -> bool {
294        matches!(self, Self::BackfillAction(_))
295    }
296}
297
298impl<N: NodePrimitives> From<ConsensusEngineEvent<N>> for EngineApiEvent<N> {
299    fn from(event: ConsensusEngineEvent<N>) -> Self {
300        Self::BeaconConsensus(event)
301    }
302}
303
304/// Events received from the engine.
305#[derive(Debug)]
306pub enum FromEngine<Req, B: Block> {
307    /// Event from the top level orchestrator.
308    Event(FromOrchestrator),
309    /// Request from the engine.
310    Request(Req),
311    /// Downloaded blocks from the network.
312    DownloadedBlocks(Vec<SealedBlockWithAccessList<B>>),
313}
314
315impl<Req: Display, B: Block> Display for FromEngine<Req, B> {
316    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317        match self {
318            Self::Event(ev) => write!(f, "Event({ev:?})"),
319            Self::Request(req) => write!(f, "Request({req})"),
320            Self::DownloadedBlocks(blocks) => {
321                write!(f, "DownloadedBlocks({} blocks)", blocks.len())
322            }
323        }
324    }
325}
326
327impl<Req, B: Block> From<FromOrchestrator> for FromEngine<Req, B> {
328    fn from(event: FromOrchestrator) -> Self {
329        Self::Event(event)
330    }
331}
332
333/// Requests produced by a [`EngineRequestHandler`].
334#[derive(Debug)]
335pub enum RequestHandlerEvent<T> {
336    /// An event emitted by the handler.
337    HandlerEvent(HandlerEvent<T>),
338    /// Request to download blocks.
339    Download(DownloadRequest),
340}
341
342/// A request to download blocks from the network.
343#[derive(Debug)]
344pub enum DownloadRequest {
345    /// Download the given set of blocks.
346    BlockSet {
347        /// The hashes of the blocks to download.
348        hashes: B256Set,
349        /// Whether to also attempt to download the blocks' access lists.
350        access_lists: bool,
351    },
352    /// Download the given range of blocks.
353    BlockRange {
354        /// The hash of the highest block of the range.
355        hash: B256,
356        /// The number of blocks to download.
357        count: u64,
358        /// Whether to also attempt to download the blocks' access lists.
359        access_lists: bool,
360    },
361}
362
363impl DownloadRequest {
364    /// Returns a [`DownloadRequest`] for a single block.
365    pub fn single_block(hash: B256) -> Self {
366        Self::block_set(B256Set::from_iter([hash]))
367    }
368
369    /// Returns a [`DownloadRequest`] for the given set of blocks.
370    pub const fn block_set(hashes: B256Set) -> Self {
371        Self::BlockSet { hashes, access_lists: false }
372    }
373
374    /// Returns a [`DownloadRequest`] for the given range of blocks.
375    pub const fn block_range(hash: B256, count: u64) -> Self {
376        Self::BlockRange { hash, count, access_lists: false }
377    }
378
379    /// Configures whether the download should also attempt to fetch the blocks' access lists.
380    ///
381    /// Access list downloads are best-effort: blocks are returned without access list data if no
382    /// peer can serve them.
383    pub const fn with_access_lists(mut self, access_lists: bool) -> Self {
384        match &mut self {
385            Self::BlockSet { access_lists: al, .. } | Self::BlockRange { access_lists: al, .. } => {
386                *al = access_lists
387            }
388        }
389        self
390    }
391}