reth_exex_types/
notification.rs

1use std::sync::Arc;
2
3use reth_chain_state::CanonStateNotification;
4use reth_execution_types::Chain;
5use reth_primitives_traits::NodePrimitives;
6
7/// Notifications sent to an `ExEx`.
8#[derive(Debug, Clone, PartialEq, Eq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub enum ExExNotification<N: NodePrimitives = reth_chain_state::EthPrimitives> {
11    /// Chain got committed without a reorg, and only the new chain is returned.
12    ChainCommitted {
13        /// The new chain after commit.
14        new: Arc<Chain<N>>,
15    },
16    /// Chain got reorged, and both the old and the new chains are returned.
17    ChainReorged {
18        /// The old chain before reorg.
19        old: Arc<Chain<N>>,
20        /// The new chain after reorg.
21        new: Arc<Chain<N>>,
22    },
23    /// Chain got reverted, and only the old chain is returned.
24    ChainReverted {
25        /// The old chain before reversion.
26        old: Arc<Chain<N>>,
27    },
28}
29
30impl<N: NodePrimitives> ExExNotification<N> {
31    /// Returns the committed chain from the [`Self::ChainCommitted`] and [`Self::ChainReorged`]
32    /// variants, if any.
33    pub fn committed_chain(&self) -> Option<Arc<Chain<N>>> {
34        match self {
35            Self::ChainCommitted { new } | Self::ChainReorged { old: _, new } => Some(new.clone()),
36            Self::ChainReverted { .. } => None,
37        }
38    }
39
40    /// Returns the reverted chain from the [`Self::ChainReorged`] and [`Self::ChainReverted`]
41    /// variants, if any.
42    pub fn reverted_chain(&self) -> Option<Arc<Chain<N>>> {
43        match self {
44            Self::ChainReorged { old, new: _ } | Self::ChainReverted { old } => Some(old.clone()),
45            Self::ChainCommitted { .. } => None,
46        }
47    }
48
49    /// Converts the notification into a notification that is the inverse of the original one.
50    ///
51    /// - For [`Self::ChainCommitted`], it's [`Self::ChainReverted`].
52    /// - For [`Self::ChainReverted`], it's [`Self::ChainCommitted`].
53    /// - For [`Self::ChainReorged`], it's [`Self::ChainReorged`] with the new chain as the old
54    ///   chain and the old chain as the new chain.
55    pub fn into_inverted(self) -> Self {
56        match self {
57            Self::ChainCommitted { new } => Self::ChainReverted { old: new },
58            Self::ChainReverted { old } => Self::ChainCommitted { new: old },
59            Self::ChainReorged { old, new } => Self::ChainReorged { old: new, new: old },
60        }
61    }
62}
63
64impl<P: NodePrimitives> From<CanonStateNotification<P>> for ExExNotification<P> {
65    fn from(notification: CanonStateNotification<P>) -> Self {
66        match notification {
67            CanonStateNotification::Commit { new } => Self::ChainCommitted { new },
68            CanonStateNotification::Reorg { old, new } => Self::ChainReorged { old, new },
69        }
70    }
71}
72
73/// Bincode-compatible [`ExExNotification`] serde implementation.
74#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
75pub(super) mod serde_bincode_compat {
76    use reth_execution_types::serde_bincode_compat::Chain;
77    use reth_primitives_traits::NodePrimitives;
78    use serde::{Deserialize, Deserializer, Serialize, Serializer};
79    use serde_with::{DeserializeAs, SerializeAs};
80    use std::sync::Arc;
81
82    /// Bincode-compatible [`super::ExExNotification`] serde implementation.
83    ///
84    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
85    /// ```rust
86    /// use reth_exex_types::{serde_bincode_compat, ExExNotification};
87    /// use reth_primitives_traits::NodePrimitives;
88    /// use serde::{Deserialize, Serialize};
89    /// use serde_with::serde_as;
90    ///
91    /// #[serde_as]
92    /// #[derive(Serialize, Deserialize)]
93    /// struct Data<N: NodePrimitives> {
94    ///     #[serde_as(as = "serde_bincode_compat::ExExNotification<'_, N>")]
95    ///     notification: ExExNotification<N>,
96    /// }
97    /// ```
98    ///
99    /// This enum mirrors [`super::ExExNotification`] but uses borrowed [`Chain`] types
100    /// instead of `Arc<Chain>` for bincode compatibility.
101    #[derive(Debug, Serialize, Deserialize)]
102    #[serde(bound = "")]
103    #[expect(clippy::large_enum_variant)]
104    pub enum ExExNotification<'a, N>
105    where
106        N: NodePrimitives,
107    {
108        /// Chain got committed without a reorg, and only the new chain is returned.
109        ChainCommitted {
110            /// The new chain after commit.
111            new: Chain<'a, N>,
112        },
113        /// Chain got reorged, and both the old and the new chains are returned.
114        ChainReorged {
115            /// The old chain before reorg.
116            old: Chain<'a, N>,
117            /// The new chain after reorg.
118            new: Chain<'a, N>,
119        },
120        /// Chain got reverted, and only the old chain is returned.
121        ChainReverted {
122            /// The old chain before reversion.
123            old: Chain<'a, N>,
124        },
125    }
126
127    impl<'a, N> From<&'a super::ExExNotification<N>> for ExExNotification<'a, N>
128    where
129        N: NodePrimitives,
130    {
131        fn from(value: &'a super::ExExNotification<N>) -> Self {
132            match value {
133                super::ExExNotification::ChainCommitted { new } => {
134                    ExExNotification::ChainCommitted { new: Chain::from(new.as_ref()) }
135                }
136                super::ExExNotification::ChainReorged { old, new } => {
137                    ExExNotification::ChainReorged {
138                        old: Chain::from(old.as_ref()),
139                        new: Chain::from(new.as_ref()),
140                    }
141                }
142                super::ExExNotification::ChainReverted { old } => {
143                    ExExNotification::ChainReverted { old: Chain::from(old.as_ref()) }
144                }
145            }
146        }
147    }
148
149    impl<'a, N> From<ExExNotification<'a, N>> for super::ExExNotification<N>
150    where
151        N: NodePrimitives,
152    {
153        fn from(value: ExExNotification<'a, N>) -> Self {
154            match value {
155                ExExNotification::ChainCommitted { new } => {
156                    Self::ChainCommitted { new: Arc::new(new.into()) }
157                }
158                ExExNotification::ChainReorged { old, new } => {
159                    Self::ChainReorged { old: Arc::new(old.into()), new: Arc::new(new.into()) }
160                }
161                ExExNotification::ChainReverted { old } => {
162                    Self::ChainReverted { old: Arc::new(old.into()) }
163                }
164            }
165        }
166    }
167
168    impl<N> SerializeAs<super::ExExNotification<N>> for ExExNotification<'_, N>
169    where
170        N: NodePrimitives,
171    {
172        fn serialize_as<S>(
173            source: &super::ExExNotification<N>,
174            serializer: S,
175        ) -> Result<S::Ok, S::Error>
176        where
177            S: Serializer,
178        {
179            ExExNotification::from(source).serialize(serializer)
180        }
181    }
182
183    impl<'de, N> DeserializeAs<'de, super::ExExNotification<N>> for ExExNotification<'de, N>
184    where
185        N: NodePrimitives,
186    {
187        fn deserialize_as<D>(deserializer: D) -> Result<super::ExExNotification<N>, D::Error>
188        where
189            D: Deserializer<'de>,
190        {
191            ExExNotification::deserialize(deserializer).map(Into::into)
192        }
193    }
194
195    #[cfg(test)]
196    mod tests {
197        use super::super::{serde_bincode_compat, ExExNotification};
198        use arbitrary::Arbitrary;
199        use rand::Rng;
200        use reth_execution_types::Chain;
201        use reth_primitives_traits::RecoveredBlock;
202        use serde::{Deserialize, Serialize};
203        use serde_with::serde_as;
204        use std::sync::Arc;
205
206        #[test]
207        fn test_exex_notification_bincode_roundtrip() {
208            #[serde_as]
209            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
210            struct Data {
211                #[serde_as(
212                    as = "serde_bincode_compat::ExExNotification<'_, reth_ethereum_primitives::EthPrimitives>"
213                )]
214                notification: ExExNotification,
215            }
216
217            let mut bytes = [0u8; 1024];
218            rand::rng().fill(bytes.as_mut_slice());
219            let data = Data {
220                notification: ExExNotification::ChainReorged {
221                    old: Arc::new(Chain::new(
222                        vec![RecoveredBlock::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
223                            .unwrap()],
224                        Default::default(),
225                        None,
226                    )),
227                    new: Arc::new(Chain::new(
228                        vec![RecoveredBlock::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
229                            .unwrap()],
230                        Default::default(),
231                        None,
232                    )),
233                },
234            };
235
236            let encoded = bincode::serialize(&data).unwrap();
237            let decoded: Data = bincode::deserialize(&encoded).unwrap();
238            assert_eq!(decoded, data);
239        }
240    }
241}