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::TxEip4844;
149    use alloy_eips::eip4895::Withdrawals;
150    use serde::{Deserialize, Deserializer, Serialize, Serializer};
151    use serde_with::{DeserializeAs, SerializeAs};
152
153    /// Bincode-compatible [`alloy_consensus::Block`] serde implementation.
154    ///
155    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
156    /// ```rust
157    /// use alloy_consensus::Block;
158    /// use reth_primitives_traits::serde_bincode_compat::{self, SerdeBincodeCompat};
159    /// use serde::{Deserialize, Serialize};
160    /// use serde_with::serde_as;
161    ///
162    /// #[serde_as]
163    /// #[derive(Serialize, Deserialize)]
164    /// struct Data<T: SerdeBincodeCompat, H: SerdeBincodeCompat> {
165    ///     #[serde_as(as = "serde_bincode_compat::Block<'_, T, H>")]
166    ///     body: Block<T, H>,
167    /// }
168    /// ```
169    #[derive(derive_more::Debug, Serialize, Deserialize)]
170    #[debug(bound())]
171    pub struct Block<'a, T: SerdeBincodeCompat, H: SerdeBincodeCompat> {
172        header: H::BincodeRepr<'a>,
173        #[serde(bound = "BlockBody<'a, T, H>: Serialize + serde::de::DeserializeOwned")]
174        body: BlockBody<'a, T, H>,
175    }
176
177    impl<'a, T: SerdeBincodeCompat, H: SerdeBincodeCompat> From<&'a alloy_consensus::Block<T, H>>
178        for Block<'a, T, H>
179    {
180        fn from(value: &'a alloy_consensus::Block<T, H>) -> Self {
181            Self { header: value.header.as_repr(), body: (&value.body).into() }
182        }
183    }
184
185    impl<'a, T: SerdeBincodeCompat, H: SerdeBincodeCompat> From<Block<'a, T, H>>
186        for alloy_consensus::Block<T, H>
187    {
188        fn from(value: Block<'a, T, H>) -> Self {
189            Self { header: SerdeBincodeCompat::from_repr(value.header), body: value.body.into() }
190        }
191    }
192
193    impl<T: SerdeBincodeCompat, H: SerdeBincodeCompat> SerializeAs<alloy_consensus::Block<T, H>>
194        for Block<'_, T, H>
195    {
196        fn serialize_as<S>(
197            source: &alloy_consensus::Block<T, H>,
198            serializer: S,
199        ) -> Result<S::Ok, S::Error>
200        where
201            S: Serializer,
202        {
203            Block::from(source).serialize(serializer)
204        }
205    }
206
207    impl<'de, T: SerdeBincodeCompat, H: SerdeBincodeCompat>
208        DeserializeAs<'de, alloy_consensus::Block<T, H>> for Block<'de, T, H>
209    {
210        fn deserialize_as<D>(deserializer: D) -> Result<alloy_consensus::Block<T, H>, D::Error>
211        where
212            D: Deserializer<'de>,
213        {
214            Block::deserialize(deserializer).map(Into::into)
215        }
216    }
217
218    impl<T: SerdeBincodeCompat, H: SerdeBincodeCompat> SerdeBincodeCompat
219        for alloy_consensus::Block<T, H>
220    {
221        type BincodeRepr<'a> = Block<'a, T, H>;
222
223        fn as_repr(&self) -> Self::BincodeRepr<'_> {
224            self.into()
225        }
226
227        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
228            repr.into()
229        }
230    }
231
232    /// Bincode-compatible [`alloy_consensus::BlockBody`] serde implementation.
233    ///
234    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
235    /// ```rust
236    /// use reth_primitives_traits::serde_bincode_compat::{self, SerdeBincodeCompat};
237    /// use serde::{Deserialize, Serialize};
238    /// use serde_with::serde_as;
239    ///
240    /// #[serde_as]
241    /// #[derive(Serialize, Deserialize)]
242    /// struct Data<T: SerdeBincodeCompat, H: SerdeBincodeCompat> {
243    ///     #[serde_as(as = "serde_bincode_compat::BlockBody<'_, T, H>")]
244    ///     body: alloy_consensus::BlockBody<T, H>,
245    /// }
246    /// ```
247    #[derive(derive_more::Debug, Serialize, Deserialize)]
248    #[debug(bound())]
249    pub struct BlockBody<'a, T: SerdeBincodeCompat, H: SerdeBincodeCompat> {
250        transactions: Vec<T::BincodeRepr<'a>>,
251        ommers: Vec<H::BincodeRepr<'a>>,
252        withdrawals: Cow<'a, Option<Withdrawals>>,
253    }
254
255    impl<'a, T: SerdeBincodeCompat, H: SerdeBincodeCompat>
256        From<&'a alloy_consensus::BlockBody<T, H>> for BlockBody<'a, T, H>
257    {
258        fn from(value: &'a alloy_consensus::BlockBody<T, H>) -> Self {
259            Self {
260                transactions: value.transactions.iter().map(|tx| tx.as_repr()).collect(),
261                ommers: value.ommers.iter().map(|h| h.as_repr()).collect(),
262                withdrawals: Cow::Borrowed(&value.withdrawals),
263            }
264        }
265    }
266
267    impl<'a, T: SerdeBincodeCompat, H: SerdeBincodeCompat> From<BlockBody<'a, T, H>>
268        for alloy_consensus::BlockBody<T, H>
269    {
270        fn from(value: BlockBody<'a, T, H>) -> Self {
271            Self {
272                transactions: value
273                    .transactions
274                    .into_iter()
275                    .map(SerdeBincodeCompat::from_repr)
276                    .collect(),
277                ommers: value.ommers.into_iter().map(SerdeBincodeCompat::from_repr).collect(),
278                withdrawals: value.withdrawals.into_owned(),
279            }
280        }
281    }
282
283    impl<T: SerdeBincodeCompat, H: SerdeBincodeCompat> SerializeAs<alloy_consensus::BlockBody<T, H>>
284        for BlockBody<'_, T, H>
285    {
286        fn serialize_as<S>(
287            source: &alloy_consensus::BlockBody<T, H>,
288            serializer: S,
289        ) -> Result<S::Ok, S::Error>
290        where
291            S: Serializer,
292        {
293            BlockBody::from(source).serialize(serializer)
294        }
295    }
296
297    impl<'de, T: SerdeBincodeCompat, H: SerdeBincodeCompat>
298        DeserializeAs<'de, alloy_consensus::BlockBody<T, H>> for BlockBody<'de, T, H>
299    {
300        fn deserialize_as<D>(deserializer: D) -> Result<alloy_consensus::BlockBody<T, H>, D::Error>
301        where
302            D: Deserializer<'de>,
303        {
304            BlockBody::deserialize(deserializer).map(Into::into)
305        }
306    }
307
308    impl<T: SerdeBincodeCompat, H: SerdeBincodeCompat> SerdeBincodeCompat
309        for alloy_consensus::BlockBody<T, H>
310    {
311        type BincodeRepr<'a> = BlockBody<'a, T, H>;
312
313        fn as_repr(&self) -> Self::BincodeRepr<'_> {
314            self.into()
315        }
316
317        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
318            repr.into()
319        }
320    }
321
322    impl super::SerdeBincodeCompat for alloy_consensus::EthereumTxEnvelope<TxEip4844> {
323        type BincodeRepr<'a> =
324            alloy_consensus::serde_bincode_compat::transaction::EthereumTxEnvelope<'a>;
325
326        fn as_repr(&self) -> Self::BincodeRepr<'_> {
327            self.into()
328        }
329
330        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
331            repr.into()
332        }
333    }
334
335    #[cfg(feature = "op")]
336    impl super::SerdeBincodeCompat for op_alloy_consensus::OpTxEnvelope {
337        type BincodeRepr<'a> =
338            op_alloy_consensus::serde_bincode_compat::transaction::OpTxEnvelope<'a>;
339
340        fn as_repr(&self) -> Self::BincodeRepr<'_> {
341            self.into()
342        }
343
344        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
345            repr.into()
346        }
347    }
348}