Skip to main content

reth_exex/
context.rs

1use crate::{ExExContextDyn, ExExEvent, ExExNotifications, ExExNotificationsStream};
2use alloy_eips::BlockNumHash;
3use reth_exex_types::ExExHead;
4use reth_node_api::{FullNodeComponents, NodeTypes, PrimitivesTy};
5use reth_node_core::node_config::NodeConfig;
6use reth_payload_builder::PayloadBuilderHandle;
7use reth_tasks::TaskExecutor;
8use std::fmt::Debug;
9use tokio::sync::mpsc::{error::SendError, UnboundedSender};
10
11/// Captures the context that an `ExEx` has access to.
12///
13/// This type wraps various node components that the `ExEx` has access to.
14pub struct ExExContext<Node: FullNodeComponents> {
15    /// The current head of the blockchain at launch.
16    pub head: BlockNumHash,
17    /// The config of the node
18    pub config: NodeConfig<<Node::Types as NodeTypes>::ChainSpec>,
19    /// The loaded node config
20    pub reth_config: reth_config::Config,
21    /// Channel used to send [`ExExEvent`]s to the rest of the node.
22    ///
23    /// # Important
24    ///
25    /// The exex should emit a `FinishedHeight` whenever a processed block is safe to prune.
26    /// Additionally, the exex can preemptively emit a `FinishedHeight` event to specify what
27    /// blocks to receive notifications for.
28    pub events: UnboundedSender<ExExEvent>,
29    /// Channel to receive [`ExExNotification`](crate::ExExNotification)s.
30    ///
31    /// # Important
32    ///
33    /// Once an [`ExExNotification`](crate::ExExNotification) is sent over the channel, it is
34    /// considered delivered by the node.
35    pub notifications: ExExNotifications<Node::Provider, Node::Evm>,
36
37    /// Node components
38    pub components: Node,
39}
40
41impl<Node> Debug for ExExContext<Node>
42where
43    Node: FullNodeComponents,
44{
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        f.debug_struct("ExExContext")
47            .field("head", &self.head)
48            .field("config", &self.config)
49            .field("reth_config", &self.reth_config)
50            .field("events", &self.events)
51            .field("notifications", &self.notifications)
52            .field("components", &"...")
53            .finish()
54    }
55}
56
57impl<Node> ExExContext<Node>
58where
59    Node: FullNodeComponents,
60{
61    /// Returns dynamic version of the context
62    pub fn into_dyn(self) -> ExExContextDyn<PrimitivesTy<Node::Types>> {
63        ExExContextDyn::from(self)
64    }
65}
66
67impl<Node> ExExContext<Node>
68where
69    Node: FullNodeComponents,
70{
71    /// Returns the transaction pool of the node.
72    pub fn pool(&self) -> &Node::Pool {
73        self.components.pool()
74    }
75
76    /// Returns the node's evm config.
77    pub fn evm_config(&self) -> &Node::Evm {
78        self.components.evm_config()
79    }
80
81    /// Returns the provider of the node.
82    pub fn provider(&self) -> &Node::Provider {
83        self.components.provider()
84    }
85
86    /// Returns the handle to the network
87    pub fn network(&self) -> &Node::Network {
88        self.components.network()
89    }
90
91    /// Returns the handle to the payload builder service.
92    pub fn payload_builder_handle(
93        &self,
94    ) -> &PayloadBuilderHandle<<Node::Types as NodeTypes>::Payload> {
95        self.components.payload_builder_handle()
96    }
97
98    /// Returns the task executor.
99    ///
100    /// This type should be used to spawn (critical) tasks.
101    pub fn task_executor(&self) -> &TaskExecutor {
102        self.components.task_executor()
103    }
104
105    /// Sets notifications stream to [`crate::ExExNotificationsWithoutHead`], a stream of
106    /// notifications without a head.
107    pub fn set_notifications_without_head(&mut self) {
108        self.notifications.set_without_head();
109    }
110
111    /// Sets notifications stream to [`crate::ExExNotificationsWithHead`], a stream of notifications
112    /// with the provided head.
113    pub fn set_notifications_with_head(&mut self, head: ExExHead) {
114        self.notifications.set_with_head(head);
115    }
116
117    /// As [`set_notifications_with_head`](Self::set_notifications_with_head), but backfills up to
118    /// the node's current canonical head rather than the head captured at launch.
119    pub fn catch_up_notifications_with_head(&mut self, head: ExExHead) -> eyre::Result<()> {
120        self.notifications.catch_up_with_head(head)
121    }
122
123    /// Sends an [`ExExEvent::FinishedHeight`] to the ExEx task manager letting it know that this
124    /// ExEx has processed the corresponding block.
125    ///
126    /// Returns an error if the channel was closed (ExEx task manager panicked).
127    pub fn send_finished_height(
128        &self,
129        height: BlockNumHash,
130    ) -> Result<(), SendError<BlockNumHash>> {
131        self.events.send(ExExEvent::FinishedHeight(height)).map_err(|_| SendError(height))
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use crate::ExExContext;
138    use reth_exex_types::ExExHead;
139    use reth_node_api::FullNodeComponents;
140
141    /// <https://github.com/paradigmxyz/reth/issues/12054>
142    #[test]
143    const fn issue_12054() {
144        #[expect(dead_code)]
145        struct ExEx<Node: FullNodeComponents> {
146            ctx: ExExContext<Node>,
147        }
148
149        impl<Node: FullNodeComponents> ExEx<Node> {
150            async fn _test_bounds(mut self) -> eyre::Result<()> {
151                self.ctx.pool();
152                self.ctx.evm_config();
153                self.ctx.provider();
154                self.ctx.network();
155                self.ctx.payload_builder_handle();
156                self.ctx.task_executor();
157                self.ctx.set_notifications_without_head();
158                self.ctx.set_notifications_with_head(ExExHead { block: Default::default() });
159                Ok(())
160            }
161        }
162    }
163}