feat(exex, primitives): serde bincode compatibility (#11331)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Alexey Shekhirin
2024-10-01 00:20:43 +03:00
committed by GitHub
parent 6e1cc1b948
commit d6113e1040
17 changed files with 995 additions and 49 deletions

View File

@ -13,7 +13,8 @@ workspace = true
[dependencies]
# reth
reth-provider.workspace = true
reth-chain-state.workspace = true
reth-execution-types.workspace = true
# reth
alloy-primitives.workspace = true
@ -21,7 +22,16 @@ alloy-eips.workspace = true
# misc
serde = { workspace = true, optional = true }
serde_with = { workspace = true, optional = true }
[dev-dependencies]
reth-primitives = { workspace = true, features = ["arbitrary"] }
arbitrary.workspace = true
bincode.workspace = true
rand.workspace = true
[features]
default = []
serde = ["dep:serde", "reth-provider/serde"]
serde = ["dep:serde", "reth-execution-types/serde"]
serde-bincode-compat = ["reth-execution-types/serde-bincode-compat", "serde_with"]

View File

@ -1,4 +1,4 @@
//! Commonly used types for exex usage.
//! Commonly used ExEx types.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
@ -15,3 +15,15 @@ mod notification;
pub use finished_height::FinishedExExHeight;
pub use head::ExExHead;
pub use notification::ExExNotification;
/// Bincode-compatible serde implementations for commonly used ExEx types.
///
/// `bincode` crate doesn't work with optionally serializable serde fields, but some of the
/// ExEx types require optional serialization for RPC compatibility. This module makes so that
/// all fields are serialized.
///
/// Read more: <https://github.com/bincode-org/bincode/issues/326>
#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
pub mod serde_bincode_compat {
pub use super::notification::serde_bincode_compat::*;
}

View File

@ -1,6 +1,7 @@
use std::sync::Arc;
use reth_provider::{CanonStateNotification, Chain};
use reth_chain_state::CanonStateNotification;
use reth_execution_types::Chain;
/// Notifications sent to an `ExEx`.
#[derive(Debug, Clone, PartialEq, Eq)]
@ -67,3 +68,143 @@ impl From<CanonStateNotification> for ExExNotification {
}
}
}
/// Bincode-compatible [`ExExNotification`] serde implementation.
#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
pub(super) mod serde_bincode_compat {
use std::sync::Arc;
use reth_execution_types::serde_bincode_compat::Chain;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::{DeserializeAs, SerializeAs};
/// Bincode-compatible [`super::ExExNotification`] serde implementation.
///
/// Intended to use with the [`serde_with::serde_as`] macro in the following way:
/// ```rust
/// use reth_exex_types::{serde_bincode_compat, ExExNotification};
/// use serde::{Deserialize, Serialize};
/// use serde_with::serde_as;
///
/// #[serde_as]
/// #[derive(Serialize, Deserialize)]
/// struct Data {
/// #[serde_as(as = "serde_bincode_compat::ExExNotification")]
/// notification: ExExNotification,
/// }
/// ```
#[derive(Debug, Serialize, Deserialize)]
#[allow(missing_docs)]
pub enum ExExNotification<'a> {
ChainCommitted { new: Chain<'a> },
ChainReorged { old: Chain<'a>, new: Chain<'a> },
ChainReverted { old: Chain<'a> },
}
impl<'a> From<&'a super::ExExNotification> for ExExNotification<'a> {
fn from(value: &'a super::ExExNotification) -> Self {
match value {
super::ExExNotification::ChainCommitted { new } => {
ExExNotification::ChainCommitted { new: Chain::from(new.as_ref()) }
}
super::ExExNotification::ChainReorged { old, new } => {
ExExNotification::ChainReorged {
old: Chain::from(old.as_ref()),
new: Chain::from(new.as_ref()),
}
}
super::ExExNotification::ChainReverted { old } => {
ExExNotification::ChainReverted { old: Chain::from(old.as_ref()) }
}
}
}
}
impl<'a> From<ExExNotification<'a>> for super::ExExNotification {
fn from(value: ExExNotification<'a>) -> Self {
match value {
ExExNotification::ChainCommitted { new } => {
Self::ChainCommitted { new: Arc::new(new.into()) }
}
ExExNotification::ChainReorged { old, new } => {
Self::ChainReorged { old: Arc::new(old.into()), new: Arc::new(new.into()) }
}
ExExNotification::ChainReverted { old } => {
Self::ChainReverted { old: Arc::new(old.into()) }
}
}
}
}
impl<'a> SerializeAs<super::ExExNotification> for ExExNotification<'a> {
fn serialize_as<S>(
source: &super::ExExNotification,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
ExExNotification::from(source).serialize(serializer)
}
}
impl<'de> DeserializeAs<'de, super::ExExNotification> for ExExNotification<'de> {
fn deserialize_as<D>(deserializer: D) -> Result<super::ExExNotification, D::Error>
where
D: Deserializer<'de>,
{
ExExNotification::deserialize(deserializer).map(Into::into)
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use arbitrary::Arbitrary;
use rand::Rng;
use reth_execution_types::Chain;
use reth_primitives::SealedBlockWithSenders;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use super::super::{serde_bincode_compat, ExExNotification};
#[test]
fn test_exex_notification_bincode_roundtrip() {
#[serde_as]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Data {
#[serde_as(as = "serde_bincode_compat::ExExNotification")]
notification: ExExNotification,
}
let mut bytes = [0u8; 1024];
rand::thread_rng().fill(bytes.as_mut_slice());
let data = Data {
notification: ExExNotification::ChainReorged {
old: Arc::new(Chain::new(
vec![SealedBlockWithSenders::arbitrary(&mut arbitrary::Unstructured::new(
&bytes,
))
.unwrap()],
Default::default(),
None,
)),
new: Arc::new(Chain::new(
vec![SealedBlockWithSenders::arbitrary(&mut arbitrary::Unstructured::new(
&bytes,
))
.unwrap()],
Default::default(),
None,
)),
},
};
let encoded = bincode::serialize(&data).unwrap();
let decoded: Data = bincode::deserialize(&encoded).unwrap();
assert_eq!(decoded, data);
}
}
}