Skip to main content

reth_primitives_traits/
serde_bincode_compat.rs

1//! Bincode compatibility support for reth primitive types.
2//!
3//! This module provides traits and implementations to work around bincode's limitations
4//! with optional serde fields. The bincode crate requires all fields to be present during
5//! serialization, which conflicts with types that have `#[serde(skip_serializing_if)]`
6//! attributes for RPC compatibility.
7//!
8//! # Overview
9//!
10//! The main trait is `SerdeBincodeCompat`, which provides a conversion mechanism between
11//! types and their bincode-compatible representations. There are two main ways to implement
12//! this trait:
13//!
14//! 1. **Using RLP encoding** - Implement `RlpBincode` for types that already support RLP
15//! 2. **Custom implementation** - Define a custom representation type
16//!
17//! # Examples
18//!
19//! ## Using with `serde_with`
20//!
21//! ```rust
22//! # use reth_primitives_traits::serde_bincode_compat::{self, SerdeBincodeCompat};
23//! # use serde::{Deserialize, Serialize};
24//! # use serde_with::serde_as;
25//! # use alloy_consensus::Header;
26//! #[serde_as]
27//! #[derive(Serialize, Deserialize)]
28//! struct MyStruct {
29//!     #[serde_as(as = "serde_bincode_compat::BincodeReprFor<'_, Header>")]
30//!     data: Header,
31//! }
32//! ```
33
34use alloc::vec::Vec;
35use alloy_primitives::Bytes;
36use core::fmt::Debug;
37use serde::{de::DeserializeOwned, Serialize};
38
39pub use super::{
40    block::{serde_bincode_compat as block, serde_bincode_compat::*},
41    header::{serde_bincode_compat as header, serde_bincode_compat::*},
42};
43pub use block_bincode::{Block, BlockBody};
44
45/// Trait for types that can be serialized and deserialized using bincode.
46///
47/// This trait provides a workaround for bincode's incompatibility with optional
48/// serde fields. It ensures all fields are serialized, making the type bincode-compatible.
49///
50/// # Implementation
51///
52/// The easiest way to implement this trait is using [`RlpBincode`] for RLP-encodable types:
53///
54/// ```rust
55/// # use reth_primitives_traits::serde_bincode_compat::RlpBincode;
56/// # use alloy_rlp::{RlpEncodable, RlpDecodable};
57/// # #[derive(RlpEncodable, RlpDecodable)]
58/// # struct MyType;
59/// impl RlpBincode for MyType {}
60/// // SerdeBincodeCompat is automatically implemented
61/// ```
62///
63/// For custom implementations, see the examples in the `block` module.
64///
65/// The recommended way to add bincode compatible serialization is via the
66/// [`serde_with`] crate and the `serde_as` macro. See for reference [`header`].
67pub trait SerdeBincodeCompat: Sized + 'static {
68    /// Serde representation of the type for bincode serialization.
69    ///
70    /// This type defines the bincode compatible serde format for the type.
71    type BincodeRepr<'a>: Debug + Serialize + DeserializeOwned;
72
73    /// Convert this type into its bincode representation
74    fn as_repr(&self) -> Self::BincodeRepr<'_>;
75
76    /// Convert from the bincode representation
77    fn from_repr(repr: Self::BincodeRepr<'_>) -> Self;
78}
79
80impl SerdeBincodeCompat for alloy_consensus::Header {
81    type BincodeRepr<'a> = alloy_consensus::serde_bincode_compat::Header<'a>;
82
83    fn as_repr(&self) -> Self::BincodeRepr<'_> {
84        self.into()
85    }
86
87    fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
88        repr.into()
89    }
90}
91
92/// Type alias for the [`SerdeBincodeCompat::BincodeRepr`] associated type.
93///
94/// This provides a convenient way to refer to the bincode representation type
95/// without having to write out the full associated type projection.
96///
97/// # Example
98///
99/// ```rust
100/// # use reth_primitives_traits::serde_bincode_compat::{SerdeBincodeCompat, BincodeReprFor};
101/// fn serialize_to_bincode<T: SerdeBincodeCompat>(value: &T) -> BincodeReprFor<'_, T> {
102///     value.as_repr()
103/// }
104/// ```
105pub type BincodeReprFor<'a, T> = <T as SerdeBincodeCompat>::BincodeRepr<'a>;
106
107/// A helper trait for using RLP-encoding for providing bincode-compatible serialization.
108///
109/// By implementing this trait, [`SerdeBincodeCompat`] will be automatically implemented for the
110/// type and RLP encoding will be used for serialization and deserialization for bincode
111/// compatibility.
112///
113/// # Example
114///
115/// ```rust
116/// # use reth_primitives_traits::serde_bincode_compat::RlpBincode;
117/// # use alloy_rlp::{RlpEncodable, RlpDecodable};
118/// #[derive(RlpEncodable, RlpDecodable)]
119/// struct MyCustomType {
120///     value: u64,
121///     data: Vec<u8>,
122/// }
123///
124/// // Simply implement the marker trait
125/// impl RlpBincode for MyCustomType {}
126///
127/// // Now MyCustomType can be used with bincode through RLP encoding
128/// ```
129pub trait RlpBincode: alloy_rlp::Encodable + alloy_rlp::Decodable {}
130
131impl<T: RlpBincode + 'static> SerdeBincodeCompat for T {
132    type BincodeRepr<'a> = Bytes;
133
134    fn as_repr(&self) -> Self::BincodeRepr<'_> {
135        let mut buf = Vec::new();
136        self.encode(&mut buf);
137        buf.into()
138    }
139
140    fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
141        Self::decode(&mut repr.as_ref()).expect("Failed to decode bincode rlp representation")
142    }
143}
144
145mod block_bincode {
146    use crate::serde_bincode_compat::SerdeBincodeCompat;
147    use alloc::{borrow::Cow, vec::Vec};
148    use alloy_consensus::TxTy;
149    use alloy_eips::eip4895::Withdrawals;
150    use core::fmt::Debug;
151    use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer};
152    use serde_with::{DeserializeAs, SerializeAs};
153
154    /// Bincode-compatible [`alloy_consensus::Block`] serde implementation.
155    ///
156    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
157    /// ```rust
158    /// use alloy_consensus::Block;
159    /// use reth_primitives_traits::serde_bincode_compat::{self, SerdeBincodeCompat};
160    /// use serde::{Deserialize, Serialize};
161    /// use serde_with::serde_as;
162    ///
163    /// #[serde_as]
164    /// #[derive(Serialize, Deserialize)]
165    /// struct Data<T: SerdeBincodeCompat, H: SerdeBincodeCompat> {
166    ///     #[serde_as(as = "serde_bincode_compat::Block<'_, T, H>")]
167    ///     body: Block<T, H>,
168    /// }
169    /// ```
170    #[derive(derive_more::Debug, Serialize, Deserialize)]
171    #[debug(bound())]
172    pub struct Block<'a, T: SerdeBincodeCompat, H: SerdeBincodeCompat> {
173        header: H::BincodeRepr<'a>,
174        #[serde(bound = "BlockBody<'a, T, H>: Serialize + serde::de::DeserializeOwned")]
175        body: BlockBody<'a, T, H>,
176    }
177
178    impl<'a, T: SerdeBincodeCompat, H: SerdeBincodeCompat> From<&'a alloy_consensus::Block<T, H>>
179        for Block<'a, T, H>
180    {
181        fn from(value: &'a alloy_consensus::Block<T, H>) -> Self {
182            Self { header: value.header.as_repr(), body: (&value.body).into() }
183        }
184    }
185
186    impl<'a, T: SerdeBincodeCompat, H: SerdeBincodeCompat> From<Block<'a, T, H>>
187        for alloy_consensus::Block<T, H>
188    {
189        fn from(value: Block<'a, T, H>) -> Self {
190            Self { header: SerdeBincodeCompat::from_repr(value.header), body: value.body.into() }
191        }
192    }
193
194    impl<T: SerdeBincodeCompat, H: SerdeBincodeCompat> SerializeAs<alloy_consensus::Block<T, H>>
195        for Block<'_, T, H>
196    {
197        fn serialize_as<S>(
198            source: &alloy_consensus::Block<T, H>,
199            serializer: S,
200        ) -> Result<S::Ok, S::Error>
201        where
202            S: Serializer,
203        {
204            Block::from(source).serialize(serializer)
205        }
206    }
207
208    impl<'de, T: SerdeBincodeCompat, H: SerdeBincodeCompat>
209        DeserializeAs<'de, alloy_consensus::Block<T, H>> for Block<'de, T, H>
210    {
211        fn deserialize_as<D>(deserializer: D) -> Result<alloy_consensus::Block<T, H>, D::Error>
212        where
213            D: Deserializer<'de>,
214        {
215            Block::deserialize(deserializer).map(Into::into)
216        }
217    }
218
219    impl<T: SerdeBincodeCompat, H: SerdeBincodeCompat> SerdeBincodeCompat
220        for alloy_consensus::Block<T, H>
221    {
222        type BincodeRepr<'a> = Block<'a, T, H>;
223
224        fn as_repr(&self) -> Self::BincodeRepr<'_> {
225            self.into()
226        }
227
228        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
229            repr.into()
230        }
231    }
232
233    /// Bincode-compatible [`alloy_consensus::BlockBody`] serde implementation.
234    ///
235    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
236    /// ```rust
237    /// use reth_primitives_traits::serde_bincode_compat::{self, SerdeBincodeCompat};
238    /// use serde::{Deserialize, Serialize};
239    /// use serde_with::serde_as;
240    ///
241    /// #[serde_as]
242    /// #[derive(Serialize, Deserialize)]
243    /// struct Data<T: SerdeBincodeCompat, H: SerdeBincodeCompat> {
244    ///     #[serde_as(as = "serde_bincode_compat::BlockBody<'_, T, H>")]
245    ///     body: alloy_consensus::BlockBody<T, H>,
246    /// }
247    /// ```
248    #[derive(derive_more::Debug, Serialize, Deserialize)]
249    #[debug(bound())]
250    pub struct BlockBody<'a, T: SerdeBincodeCompat, H: SerdeBincodeCompat> {
251        transactions: Vec<T::BincodeRepr<'a>>,
252        ommers: Vec<H::BincodeRepr<'a>>,
253        withdrawals: Cow<'a, Option<Withdrawals>>,
254    }
255
256    impl<'a, T: SerdeBincodeCompat, H: SerdeBincodeCompat>
257        From<&'a alloy_consensus::BlockBody<T, H>> for BlockBody<'a, T, H>
258    {
259        fn from(value: &'a alloy_consensus::BlockBody<T, H>) -> Self {
260            Self {
261                transactions: value.transactions.iter().map(|tx| tx.as_repr()).collect(),
262                ommers: value.ommers.iter().map(|h| h.as_repr()).collect(),
263                withdrawals: Cow::Borrowed(&value.withdrawals),
264            }
265        }
266    }
267
268    impl<'a, T: SerdeBincodeCompat, H: SerdeBincodeCompat> From<BlockBody<'a, T, H>>
269        for alloy_consensus::BlockBody<T, H>
270    {
271        fn from(value: BlockBody<'a, T, H>) -> Self {
272            Self {
273                transactions: value
274                    .transactions
275                    .into_iter()
276                    .map(SerdeBincodeCompat::from_repr)
277                    .collect(),
278                ommers: value.ommers.into_iter().map(SerdeBincodeCompat::from_repr).collect(),
279                withdrawals: value.withdrawals.into_owned(),
280            }
281        }
282    }
283
284    impl<T: SerdeBincodeCompat, H: SerdeBincodeCompat> SerializeAs<alloy_consensus::BlockBody<T, H>>
285        for BlockBody<'_, T, H>
286    {
287        fn serialize_as<S>(
288            source: &alloy_consensus::BlockBody<T, H>,
289            serializer: S,
290        ) -> Result<S::Ok, S::Error>
291        where
292            S: Serializer,
293        {
294            BlockBody::from(source).serialize(serializer)
295        }
296    }
297
298    impl<'de, T: SerdeBincodeCompat, H: SerdeBincodeCompat>
299        DeserializeAs<'de, alloy_consensus::BlockBody<T, H>> for BlockBody<'de, T, H>
300    {
301        fn deserialize_as<D>(deserializer: D) -> Result<alloy_consensus::BlockBody<T, H>, D::Error>
302        where
303            D: Deserializer<'de>,
304        {
305            BlockBody::deserialize(deserializer).map(Into::into)
306        }
307    }
308
309    impl<T: SerdeBincodeCompat, H: SerdeBincodeCompat> SerdeBincodeCompat
310        for alloy_consensus::BlockBody<T, H>
311    {
312        type BincodeRepr<'a> = BlockBody<'a, T, H>;
313
314        fn as_repr(&self) -> Self::BincodeRepr<'_> {
315            self.into()
316        }
317
318        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
319            repr.into()
320        }
321    }
322
323    impl<T: Clone + Serialize + DeserializeOwned + Debug + 'static> super::SerdeBincodeCompat
324        for alloy_consensus::EthereumTxEnvelope<T>
325    {
326        type BincodeRepr<'a> =
327            alloy_consensus::serde_bincode_compat::transaction::EthereumTxEnvelope<'a, T>;
328
329        fn as_repr(&self) -> Self::BincodeRepr<'_> {
330            self.into()
331        }
332
333        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
334            repr.into()
335        }
336    }
337
338    #[cfg(feature = "op")]
339    impl super::SerdeBincodeCompat for op_alloy_consensus::OpTxEnvelope {
340        type BincodeRepr<'a> =
341            op_alloy_consensus::serde_bincode_compat::transaction::OpTxEnvelope<'a>;
342
343        fn as_repr(&self) -> Self::BincodeRepr<'_> {
344            self.into()
345        }
346
347        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
348            repr.into()
349        }
350    }
351
352    impl<T: TxTy + Serialize + DeserializeOwned> super::SerdeBincodeCompat
353        for alloy_consensus::EthereumReceipt<T>
354    {
355        type BincodeRepr<'a> = alloy_consensus::serde_bincode_compat::EthereumReceipt<'a, T>;
356
357        fn as_repr(&self) -> Self::BincodeRepr<'_> {
358            self.into()
359        }
360
361        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
362            repr.into()
363        }
364    }
365
366    #[cfg(feature = "op")]
367    impl super::SerdeBincodeCompat for op_alloy_consensus::OpReceipt {
368        type BincodeRepr<'a> = op_alloy_consensus::serde_bincode_compat::OpReceipt<'a>;
369
370        fn as_repr(&self) -> Self::BincodeRepr<'_> {
371            self.into()
372        }
373
374        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
375            repr.into()
376        }
377    }
378}