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

44
Cargo.lock generated
View File

@ -124,6 +124,7 @@ dependencies = [
"c-kzg",
"derive_more",
"serde",
"serde_with",
]
[[package]]
@ -5103,9 +5104,9 @@ checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
[[package]]
name = "op-alloy-consensus"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "274ce39752bdd16614292484839eb3e62139724c15087d9175a5838dab8d6317"
checksum = "c662868734bd5a274c4474dc0642b5211f008367e591573277e5895333cb78f5"
dependencies = [
"alloy-consensus",
"alloy-eips",
@ -5115,14 +5116,15 @@ dependencies = [
"arbitrary",
"derive_more",
"serde",
"serde_with",
"spin",
]
[[package]]
name = "op-alloy-genesis"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1691c004810c0cda7e429866a8c561f21a26649f4143db61b1ce4e390493ce2"
checksum = "67b4faf4f93b34c263e66cb163a085d9da72ced1f3adb34b7bd70c6e9fc7e5d6"
dependencies = [
"alloy-consensus",
"alloy-eips",
@ -5134,9 +5136,9 @@ dependencies = [
[[package]]
name = "op-alloy-network"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f08eccaddff3ecf46c7c9850e4842ef6218481c6829b4135ce230610d0a8f679"
checksum = "a51504fd83b75b5d5e09320a0b4657b3bf23fc8018d40038ebab4eafcd7b9a40"
dependencies = [
"alloy-consensus",
"alloy-network",
@ -5148,9 +5150,9 @@ dependencies = [
[[package]]
name = "op-alloy-protocol"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20ca8f42c59b06ed0267e39279c3426576979b9e217db1d0f3f2e8f0c913fc01"
checksum = "20bec4f5aff4fe44e1e5beecd988096e6b757bd4bdfe6b10bb3f08c410287348"
dependencies = [
"alloy-consensus",
"alloy-eips",
@ -5165,9 +5167,9 @@ dependencies = [
[[package]]
name = "op-alloy-rpc-types"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "118f7e47fa822356fe4529bfa3b5d828308c1b53769d2e268337fa5b7d357929"
checksum = "971fb1d31a1764327e4cf27a5372d2fde5db8bead90f75a750eeab306979b34c"
dependencies = [
"alloy-consensus",
"alloy-eips",
@ -5182,9 +5184,9 @@ dependencies = [
[[package]]
name = "op-alloy-rpc-types-engine"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a5cbbffe83cbec46f19b184b63270c2090ce72c200cff19bc29e1f47519952"
checksum = "eb2b515967262eae36ccecf868ab123dd8a098476f08f28f8ab4c3db5e1ee306"
dependencies = [
"alloy-eips",
"alloy-primitives",
@ -7332,11 +7334,15 @@ version = "1.0.7"
dependencies = [
"alloy-eips",
"alloy-primitives",
"arbitrary",
"bincode",
"rand 0.8.5",
"reth-execution-errors",
"reth-primitives",
"reth-trie",
"revm",
"serde",
"serde_with",
]
[[package]]
@ -7421,8 +7427,14 @@ version = "1.0.7"
dependencies = [
"alloy-eips",
"alloy-primitives",
"reth-provider",
"arbitrary",
"bincode",
"rand 0.8.5",
"reth-chain-state",
"reth-execution-types",
"reth-primitives",
"serde",
"serde_with",
]
[[package]]
@ -8242,6 +8254,7 @@ dependencies = [
"alloy-serde",
"arbitrary",
"assert_matches",
"bincode",
"bytes",
"c-kzg",
"criterion",
@ -8262,11 +8275,13 @@ dependencies = [
"reth-optimism-chainspec",
"reth-primitives-traits",
"reth-static-file-types",
"reth-testing-utils",
"reth-trie-common",
"revm-primitives",
"secp256k1",
"serde",
"serde_json",
"serde_with",
"test-fuzz",
"zstd",
]
@ -8281,6 +8296,7 @@ dependencies = [
"alloy-primitives",
"alloy-rlp",
"arbitrary",
"bincode",
"byteorder",
"bytes",
"derive_more",
@ -8289,10 +8305,12 @@ dependencies = [
"proptest-arbitrary-interop",
"rand 0.8.5",
"reth-codecs",
"reth-testing-utils",
"revm-primitives",
"roaring",
"serde",
"serde_json",
"serde_with",
"test-fuzz",
]

View File

@ -461,20 +461,21 @@ alloy-transport-ipc = { version = "0.4.0", default-features = false }
alloy-transport-ws = { version = "0.4.0", default-features = false }
# op
op-alloy-rpc-types = "0.3.1"
op-alloy-rpc-types-engine = "0.3.1"
op-alloy-network = "0.3.1"
op-alloy-consensus = "0.3.1"
op-alloy-rpc-types = "0.3.2"
op-alloy-rpc-types-engine = "0.3.2"
op-alloy-network = "0.3.2"
op-alloy-consensus = "0.3.2"
# misc
aquamarine = "0.5"
auto_impl = "1"
backon = "0.4"
bincode = "1.3"
bitflags = "2.4"
boyer-moore-magiclen = "0.2.16"
bytes = "1.5"
clap = "4"
cfg-if = "1.0"
clap = "4"
const_format = { version = "0.2.32", features = ["rust_1_64"] }
dashmap = "6.0"
derive_more = { version = "1", features = ["full"] }
@ -589,7 +590,7 @@ tikv-jemalloc-ctl = "0.6"
tikv-jemallocator = "0.6"
tracy-client = "0.17.3"
#[patch.crates-io]
[patch.crates-io]
#alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
#alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}
#alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"}

View File

@ -22,13 +22,18 @@ alloy-primitives.workspace = true
alloy-eips.workspace = true
serde = { workspace = true, optional = true }
serde_with = { workspace = true, optional = true }
[dev-dependencies]
reth-primitives = { workspace = true, features = ["test-utils"] }
alloy-eips.workspace = true
arbitrary.workspace = true
bincode.workspace = true
rand.workspace = true
reth-primitives = { workspace = true, features = ["test-utils"] }
[features]
default = ["std"]
optimism = []
serde = ["dep:serde", "reth-trie/serde", "revm/serde"]
serde-bincode-compat = ["reth-primitives/serde-bincode-compat", "serde_with"]
std = []

View File

@ -723,3 +723,124 @@ mod tests {
assert_eq!(chain.execution_outcome_at_block(11), Some(execution_outcome));
}
}
/// Bincode-compatible [`Chain`] serde implementation.
#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
pub(super) mod serde_bincode_compat {
use std::collections::BTreeMap;
use alloc::borrow::Cow;
use alloy_primitives::BlockNumber;
use reth_primitives::serde_bincode_compat::SealedBlockWithSenders;
use reth_trie::updates::TrieUpdates;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::{DeserializeAs, SerializeAs};
use crate::ExecutionOutcome;
/// Bincode-compatible [`super::Chain`] serde implementation.
///
/// Intended to use with the [`serde_with::serde_as`] macro in the following way:
/// ```rust
/// use reth_execution_types::{serde_bincode_compat, Chain};
/// use serde::{Deserialize, Serialize};
/// use serde_with::serde_as;
///
/// #[serde_as]
/// #[derive(Serialize, Deserialize)]
/// struct Data {
/// #[serde_as(as = "serde_bincode_compat::Chain")]
/// chain: Chain,
/// }
/// ```
#[derive(Debug, Serialize, Deserialize)]
pub struct Chain<'a> {
blocks: BTreeMap<BlockNumber, SealedBlockWithSenders<'a>>,
execution_outcome: Cow<'a, ExecutionOutcome>,
trie_updates: Cow<'a, Option<TrieUpdates>>,
}
impl<'a> From<&'a super::Chain> for Chain<'a> {
fn from(value: &'a super::Chain) -> Self {
Self {
blocks: value
.blocks
.iter()
.map(|(block_number, block)| (*block_number, block.into()))
.collect(),
execution_outcome: Cow::Borrowed(&value.execution_outcome),
trie_updates: Cow::Borrowed(&value.trie_updates),
}
}
}
impl<'a> From<Chain<'a>> for super::Chain {
fn from(value: Chain<'a>) -> Self {
Self {
blocks: value
.blocks
.into_iter()
.map(|(block_number, block)| (block_number, block.into()))
.collect(),
execution_outcome: value.execution_outcome.into_owned(),
trie_updates: value.trie_updates.into_owned(),
}
}
}
impl<'a> SerializeAs<super::Chain> for Chain<'a> {
fn serialize_as<S>(source: &super::Chain, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
Chain::from(source).serialize(serializer)
}
}
impl<'de> DeserializeAs<'de, super::Chain> for Chain<'de> {
fn deserialize_as<D>(deserializer: D) -> Result<super::Chain, D::Error>
where
D: Deserializer<'de>,
{
Chain::deserialize(deserializer).map(Into::into)
}
}
#[cfg(test)]
mod tests {
use arbitrary::Arbitrary;
use rand::Rng;
use reth_primitives::SealedBlockWithSenders;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use super::super::{serde_bincode_compat, Chain};
#[test]
fn test_chain_bincode_roundtrip() {
#[serde_as]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Data {
#[serde_as(as = "serde_bincode_compat::Chain")]
chain: Chain,
}
let mut bytes = [0u8; 1024];
rand::thread_rng().fill(bytes.as_mut_slice());
let data = Data {
chain: 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);
}
}
}

View File

@ -18,3 +18,15 @@ pub use execute::*;
mod execution_outcome;
pub use execution_outcome::*;
/// Bincode-compatible serde implementations for commonly used types for (EVM) block execution.
///
/// `bincode` crate doesn't work with optionally serializable serde fields, but some of the
/// execution 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(feature = "serde-bincode-compat")]
pub mod serde_bincode_compat {
pub use super::chain::serde_bincode_compat::*;
}

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);
}
}
}

View File

@ -20,16 +20,17 @@ alloy-genesis.workspace = true
alloy-primitives.workspace = true
alloy-rlp.workspace = true
derive_more.workspace = true
revm-primitives = { workspace = true, features = ["serde"] }
# misc
roaring = "0.10.2"
byteorder = "1"
derive_more.workspace = true
roaring = "0.10.2"
serde_with = { workspace = true, optional = true }
# required by reth-codecs
modular-bitfield.workspace = true
bytes.workspace = true
modular-bitfield.workspace = true
serde.workspace = true
# arbitrary utils
@ -38,14 +39,18 @@ proptest = { workspace = true, optional = true }
proptest-arbitrary-interop = { workspace = true, optional = true }
[dev-dependencies]
reth-testing-utils.workspace = true
alloy-primitives = { workspace = true, features = ["arbitrary"] }
alloy-consensus = { workspace = true, features = ["arbitrary"] }
arbitrary = { workspace = true, features = ["derive"] }
proptest.workspace = true
bincode.workspace = true
proptest-arbitrary-interop.workspace = true
test-fuzz.workspace = true
proptest.workspace = true
rand.workspace = true
serde_json.workspace = true
test-fuzz.workspace = true
[features]
default = ["std"]
@ -59,3 +64,4 @@ arbitrary = [
"dep:proptest",
"dep:proptest-arbitrary-interop",
]
serde-bincode-compat = ["serde_with", "alloy-consensus/serde-bincode-compat"]

View File

@ -11,6 +11,11 @@ pub use alloy_consensus::Header;
use alloy_primitives::{Address, BlockNumber, B256, U256};
#[cfg(feature = "serde-bincode-compat")]
pub(super) mod serde_bincode_compat {
pub use super::sealed::serde_bincode_compat::SealedHeader;
}
/// Trait for extracting specific Ethereum block data from a header
pub trait BlockHeader {
/// Retrieves the beneficiary (miner) of the block

View File

@ -140,3 +140,95 @@ impl<'a> arbitrary::Arbitrary<'a> for SealedHeader {
Ok(Self::new(header, seal))
}
}
/// Bincode-compatible [`SealedHeader`] serde implementation.
#[cfg(feature = "serde-bincode-compat")]
pub(super) mod serde_bincode_compat {
use alloy_consensus::serde_bincode_compat::Header;
use alloy_primitives::BlockHash;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::{DeserializeAs, SerializeAs};
/// Bincode-compatible [`super::SealedHeader`] serde implementation.
///
/// Intended to use with the [`serde_with::serde_as`] macro in the following way:
/// ```rust
/// use reth_primitives_traits::{header::SealedHeader, serde_bincode_compat};
/// use serde::{Deserialize, Serialize};
/// use serde_with::serde_as;
///
/// #[serde_as]
/// #[derive(Serialize, Deserialize)]
/// struct Data {
/// #[serde_as(as = "serde_bincode_compat::header::SealedHeader")]
/// header: SealedHeader,
/// }
/// ```
#[derive(Debug, Serialize, Deserialize)]
pub struct SealedHeader<'a> {
hash: BlockHash,
header: Header<'a>,
}
impl<'a> From<&'a super::SealedHeader> for SealedHeader<'a> {
fn from(value: &'a super::SealedHeader) -> Self {
Self { hash: value.hash, header: Header::from(&value.header) }
}
}
impl<'a> From<SealedHeader<'a>> for super::SealedHeader {
fn from(value: SealedHeader<'a>) -> Self {
Self { hash: value.hash, header: value.header.into() }
}
}
impl<'a> SerializeAs<super::SealedHeader> for SealedHeader<'a> {
fn serialize_as<S>(source: &super::SealedHeader, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
SealedHeader::from(source).serialize(serializer)
}
}
impl<'de> DeserializeAs<'de, super::SealedHeader> for SealedHeader<'de> {
fn deserialize_as<D>(deserializer: D) -> Result<super::SealedHeader, D::Error>
where
D: Deserializer<'de>,
{
SealedHeader::deserialize(deserializer).map(Into::into)
}
}
#[cfg(test)]
mod tests {
use super::super::{serde_bincode_compat, SealedHeader};
use arbitrary::Arbitrary;
use rand::Rng;
use reth_testing_utils::generators;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
#[test]
fn test_sealed_header_bincode_roundtrip() {
#[serde_as]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Data {
#[serde_as(as = "serde_bincode_compat::SealedHeader")]
transaction: SealedHeader,
}
let mut bytes = [0u8; 1024];
generators::rng().fill(bytes.as_mut_slice());
let data = Data {
transaction: SealedHeader::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
.unwrap(),
};
let encoded = bincode::serialize(&data).unwrap();
let decoded: Data = bincode::deserialize(&encoded).unwrap();
assert_eq!(decoded, data);
}
}
}

View File

@ -1,4 +1,4 @@
//! Common abstracted types in reth.
//! Common abstracted types in Reth.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
@ -43,3 +43,15 @@ pub mod header;
#[cfg(any(test, feature = "arbitrary", feature = "test-utils"))]
pub use header::test_utils;
pub use header::{BlockHeader, Header, HeaderError, SealedHeader};
/// Bincode-compatible serde implementations for common abstracted types in Reth.
///
/// `bincode` crate doesn't work with optionally serializable serde fields, but some of the
/// Reth 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(feature = "serde-bincode-compat")]
pub mod serde_bincode_compat {
pub use super::header::serde_bincode_compat::*;
}

View File

@ -51,9 +51,11 @@ c-kzg = { workspace = true, features = ["serde"], optional = true }
bytes.workspace = true
derive_more.workspace = true
modular-bitfield = { workspace = true, optional = true }
once_cell.workspace = true
rand = { workspace = true, optional = true }
rayon.workspace = true
serde.workspace = true
once_cell.workspace = true
serde_with = { workspace = true, optional = true }
zstd = { workspace = true, features = ["experimental"], optional = true }
# arbitrary utils
@ -62,22 +64,24 @@ proptest = { workspace = true, optional = true }
[dev-dependencies]
# eth
reth-primitives-traits = { workspace = true, features = ["arbitrary"] }
revm-primitives = { workspace = true, features = ["arbitrary"] }
reth-chainspec.workspace = true
reth-codecs.workspace = true
reth-primitives-traits = { workspace = true, features = ["arbitrary"] }
reth-testing-utils.workspace = true
revm-primitives = { workspace = true, features = ["arbitrary"] }
alloy-eips = { workspace = true, features = ["arbitrary"] }
alloy-genesis.workspace = true
assert_matches.workspace = true
arbitrary = { workspace = true, features = ["derive"] }
proptest.workspace = true
assert_matches.workspace = true
bincode.workspace = true
modular-bitfield.workspace = true
proptest-arbitrary-interop.workspace = true
proptest.workspace = true
rand.workspace = true
serde_json.workspace = true
test-fuzz.workspace = true
modular-bitfield.workspace = true
criterion.workspace = true
pprof = { workspace = true, features = [
@ -92,26 +96,28 @@ std = ["reth-primitives-traits/std"]
reth-codec = ["dep:reth-codecs", "dep:zstd", "dep:modular-bitfield", "std"]
asm-keccak = ["alloy-primitives/asm-keccak"]
arbitrary = [
"reth-primitives-traits/arbitrary",
"revm-primitives/arbitrary",
"reth-ethereum-forks/arbitrary",
"alloy-eips/arbitrary",
"dep:arbitrary",
"dep:proptest",
"alloy-eips/arbitrary",
"rand",
"reth-codec",
"reth-ethereum-forks/arbitrary",
"reth-primitives-traits/arbitrary",
"revm-primitives/arbitrary",
"secp256k1",
]
secp256k1 = ["dep:secp256k1"]
c-kzg = [
"dep:c-kzg",
"revm-primitives/c-kzg",
"alloy-eips/kzg",
"alloy-consensus/kzg",
"alloy-eips/kzg",
"revm-primitives/c-kzg",
]
optimism = [
"revm-primitives/optimism",
"reth-codecs?/optimism",
"dep:reth-optimism-chainspec",
"dep:op-alloy-consensus",
"dep:reth-optimism-chainspec",
"reth-codecs?/optimism",
"revm-primitives/optimism",
]
alloy-compat = [
"dep:alloy-rpc-types",
@ -119,6 +125,12 @@ alloy-compat = [
"dep:op-alloy-rpc-types",
]
test-utils = ["reth-primitives-traits/test-utils"]
serde-bincode-compat = [
"alloy-consensus/serde-bincode-compat",
"op-alloy-consensus?/serde-bincode-compat",
"reth-primitives-traits/serde-bincode-compat",
"serde_with",
]
[[bench]]
name = "recover_ecdsa_crit"

View File

@ -541,6 +541,22 @@ impl SealedBlockWithSenders {
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for SealedBlockWithSenders {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let block = SealedBlock::arbitrary(u)?;
let senders = block
.body
.transactions
.iter()
.map(|tx| tx.recover_signer().unwrap())
.collect::<Vec<_>>();
Ok(Self { block, senders })
}
}
/// A response to `GetBlockBodies`, containing bodies if any bodies were found.
///
/// Withdrawals can be optionally included at the end of the RLP encoded message.
@ -861,3 +877,257 @@ mod tests {
);
}
}
/// Bincode-compatible block type serde implementations.
#[cfg(feature = "serde-bincode-compat")]
pub(super) mod serde_bincode_compat {
use alloc::{borrow::Cow, vec::Vec};
use alloy_consensus::serde_bincode_compat::Header;
use alloy_primitives::Address;
use reth_primitives_traits::{serde_bincode_compat::SealedHeader, Requests, Withdrawals};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::{DeserializeAs, SerializeAs};
use crate::transaction::serde_bincode_compat::TransactionSigned;
/// Bincode-compatible [`super::BlockBody`] serde implementation.
///
/// Intended to use with the [`serde_with::serde_as`] macro in the following way:
/// ```rust
/// use reth_primitives::{serde_bincode_compat, BlockBody};
/// use serde::{Deserialize, Serialize};
/// use serde_with::serde_as;
///
/// #[serde_as]
/// #[derive(Serialize, Deserialize)]
/// struct Data {
/// #[serde_as(as = "serde_bincode_compat::BlockBody")]
/// body: BlockBody,
/// }
/// ```
#[derive(Debug, Serialize, Deserialize)]
pub struct BlockBody<'a> {
transactions: Vec<TransactionSigned<'a>>,
ommers: Vec<Header<'a>>,
withdrawals: Cow<'a, Option<Withdrawals>>,
requests: Cow<'a, Option<Requests>>,
}
impl<'a> From<&'a super::BlockBody> for BlockBody<'a> {
fn from(value: &'a super::BlockBody) -> Self {
Self {
transactions: value.transactions.iter().map(Into::into).collect(),
ommers: value.ommers.iter().map(Into::into).collect(),
withdrawals: Cow::Borrowed(&value.withdrawals),
requests: Cow::Borrowed(&value.requests),
}
}
}
impl<'a> From<BlockBody<'a>> for super::BlockBody {
fn from(value: BlockBody<'a>) -> Self {
Self {
transactions: value.transactions.into_iter().map(Into::into).collect(),
ommers: value.ommers.into_iter().map(Into::into).collect(),
withdrawals: value.withdrawals.into_owned(),
requests: value.requests.into_owned(),
}
}
}
impl<'a> SerializeAs<super::BlockBody> for BlockBody<'a> {
fn serialize_as<S>(source: &super::BlockBody, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
BlockBody::from(source).serialize(serializer)
}
}
impl<'de> DeserializeAs<'de, super::BlockBody> for BlockBody<'de> {
fn deserialize_as<D>(deserializer: D) -> Result<super::BlockBody, D::Error>
where
D: Deserializer<'de>,
{
BlockBody::deserialize(deserializer).map(Into::into)
}
}
/// Bincode-compatible [`super::SealedBlock`] serde implementation.
///
/// Intended to use with the [`serde_with::serde_as`] macro in the following way:
/// ```rust
/// use reth_primitives::{serde_bincode_compat, SealedBlock};
/// use serde::{Deserialize, Serialize};
/// use serde_with::serde_as;
///
/// #[serde_as]
/// #[derive(Serialize, Deserialize)]
/// struct Data {
/// #[serde_as(as = "serde_bincode_compat::SealedBlock")]
/// block: SealedBlock,
/// }
/// ```
#[derive(Debug, Serialize, Deserialize)]
pub struct SealedBlock<'a> {
header: SealedHeader<'a>,
body: BlockBody<'a>,
}
impl<'a> From<&'a super::SealedBlock> for SealedBlock<'a> {
fn from(value: &'a super::SealedBlock) -> Self {
Self { header: SealedHeader::from(&value.header), body: BlockBody::from(&value.body) }
}
}
impl<'a> From<SealedBlock<'a>> for super::SealedBlock {
fn from(value: SealedBlock<'a>) -> Self {
Self { header: value.header.into(), body: value.body.into() }
}
}
impl<'a> SerializeAs<super::SealedBlock> for SealedBlock<'a> {
fn serialize_as<S>(source: &super::SealedBlock, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
SealedBlock::from(source).serialize(serializer)
}
}
impl<'de> DeserializeAs<'de, super::SealedBlock> for SealedBlock<'de> {
fn deserialize_as<D>(deserializer: D) -> Result<super::SealedBlock, D::Error>
where
D: Deserializer<'de>,
{
SealedBlock::deserialize(deserializer).map(Into::into)
}
}
/// Bincode-compatible [`super::SealedBlockWithSenders`] serde implementation.
///
/// Intended to use with the [`serde_with::serde_as`] macro in the following way:
/// ```rust
/// use reth_primitives::{serde_bincode_compat, SealedBlockWithSenders};
/// use serde::{Deserialize, Serialize};
/// use serde_with::serde_as;
///
/// #[serde_as]
/// #[derive(Serialize, Deserialize)]
/// struct Data {
/// #[serde_as(as = "serde_bincode_compat::SealedBlockWithSenders")]
/// block: SealedBlockWithSenders,
/// }
/// ```
#[derive(Debug, Serialize, Deserialize)]
pub struct SealedBlockWithSenders<'a> {
block: SealedBlock<'a>,
senders: Cow<'a, Vec<Address>>,
}
impl<'a> From<&'a super::SealedBlockWithSenders> for SealedBlockWithSenders<'a> {
fn from(value: &'a super::SealedBlockWithSenders) -> Self {
Self { block: SealedBlock::from(&value.block), senders: Cow::Borrowed(&value.senders) }
}
}
impl<'a> From<SealedBlockWithSenders<'a>> for super::SealedBlockWithSenders {
fn from(value: SealedBlockWithSenders<'a>) -> Self {
Self { block: value.block.into(), senders: value.senders.into_owned() }
}
}
impl<'a> SerializeAs<super::SealedBlockWithSenders> for SealedBlockWithSenders<'a> {
fn serialize_as<S>(
source: &super::SealedBlockWithSenders,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
SealedBlockWithSenders::from(source).serialize(serializer)
}
}
impl<'de> DeserializeAs<'de, super::SealedBlockWithSenders> for SealedBlockWithSenders<'de> {
fn deserialize_as<D>(deserializer: D) -> Result<super::SealedBlockWithSenders, D::Error>
where
D: Deserializer<'de>,
{
SealedBlockWithSenders::deserialize(deserializer).map(Into::into)
}
}
#[cfg(test)]
mod tests {
use super::super::{serde_bincode_compat, BlockBody, SealedBlock, SealedBlockWithSenders};
use arbitrary::Arbitrary;
use rand::Rng;
use reth_testing_utils::generators;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
#[test]
fn test_block_body_bincode_roundtrip() {
#[serde_as]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Data {
#[serde_as(as = "serde_bincode_compat::BlockBody")]
block_body: BlockBody,
}
let mut bytes = [0u8; 1024];
generators::rng().fill(bytes.as_mut_slice());
let data = Data {
block_body: BlockBody::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
.unwrap(),
};
let encoded = bincode::serialize(&data).unwrap();
let decoded: Data = bincode::deserialize(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_sealed_block_bincode_roundtrip() {
#[serde_as]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Data {
#[serde_as(as = "serde_bincode_compat::SealedBlock")]
block: SealedBlock,
}
let mut bytes = [0u8; 1024];
generators::rng().fill(bytes.as_mut_slice());
let data = Data {
block: SealedBlock::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(),
};
let encoded = bincode::serialize(&data).unwrap();
let decoded: Data = bincode::deserialize(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_sealed_block_with_senders_bincode_roundtrip() {
#[serde_as]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Data {
#[serde_as(as = "serde_bincode_compat::SealedBlockWithSenders")]
block: SealedBlockWithSenders,
}
let mut bytes = [0u8; 1024];
generators::rng().fill(bytes.as_mut_slice());
let data = Data {
block: SealedBlockWithSenders::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
.unwrap(),
};
let encoded = bincode::serialize(&data).unwrap();
let decoded: Data = bincode::deserialize(&encoded).unwrap();
assert_eq!(decoded, data);
}
}
}

View File

@ -1,4 +1,4 @@
//! Commonly used types in reth.
//! Commonly used types in Reth.
//!
//! This crate contains Ethereum primitive types and helper functions.
//!
@ -87,3 +87,18 @@ mod optimism {
#[cfg(feature = "optimism")]
pub use optimism::*;
/// Bincode-compatible serde implementations for commonly used types in Reth.
///
/// `bincode` crate doesn't work with optionally serializable serde fields, but some of the
/// Reth 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(feature = "serde-bincode-compat")]
pub mod serde_bincode_compat {
pub use super::{
block::serde_bincode_compat::*,
transaction::{serde_bincode_compat as transaction, serde_bincode_compat::*},
};
}

View File

@ -1416,7 +1416,14 @@ impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
#[allow(unused_mut)]
let mut transaction = Transaction::arbitrary(u)?;
let mut signature = Signature::arbitrary(u)?;
let secp = secp256k1::Secp256k1::new();
let key_pair = secp256k1::Keypair::new(&secp, &mut rand::thread_rng());
let mut signature = crate::sign_message(
B256::from_slice(&key_pair.secret_bytes()[..]),
transaction.signature_hash(),
)
.unwrap();
signature = if matches!(transaction, Transaction::Legacy(_)) {
if let Some(chain_id) = transaction.chain_id() {
@ -1969,3 +1976,210 @@ mod tests {
assert!(res.is_err());
}
}
/// Bincode-compatible transaction type serde implementations.
#[cfg(feature = "serde-bincode-compat")]
pub mod serde_bincode_compat {
use alloc::borrow::Cow;
use alloy_consensus::{
transaction::serde_bincode_compat::{TxEip1559, TxEip2930, TxLegacy},
TxEip4844, TxEip7702,
};
use alloy_primitives::{Signature, TxHash};
#[cfg(feature = "optimism")]
use op_alloy_consensus::serde_bincode_compat::TxDeposit;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::{DeserializeAs, SerializeAs};
/// Bincode-compatible [`super::Transaction`] serde implementation.
///
/// Intended to use with the [`serde_with::serde_as`] macro in the following way:
/// ```rust
/// use reth_primitives_traits::{serde_bincode_compat, Transaction};
/// use serde::{Deserialize, Serialize};
/// use serde_with::serde_as;
///
/// #[serde_as]
/// #[derive(Serialize, Deserialize)]
/// struct Data {
/// #[serde_as(as = "serde_bincode_compat::transaction::Transaction")]
/// transaction: Transaction,
/// }
/// ```
#[derive(Debug, Serialize, Deserialize)]
#[allow(missing_docs)]
pub enum Transaction<'a> {
Legacy(TxLegacy<'a>),
Eip2930(TxEip2930<'a>),
Eip1559(TxEip1559<'a>),
Eip4844(Cow<'a, TxEip4844>),
Eip7702(Cow<'a, TxEip7702>),
#[cfg(feature = "optimism")]
#[cfg(feature = "optimism")]
Deposit(TxDeposit<'a>),
}
impl<'a> From<&'a super::Transaction> for Transaction<'a> {
fn from(value: &'a super::Transaction) -> Self {
match value {
super::Transaction::Legacy(tx) => Self::Legacy(TxLegacy::from(tx)),
super::Transaction::Eip2930(tx) => Self::Eip2930(TxEip2930::from(tx)),
super::Transaction::Eip1559(tx) => Self::Eip1559(TxEip1559::from(tx)),
super::Transaction::Eip4844(tx) => Self::Eip4844(Cow::Borrowed(tx)),
super::Transaction::Eip7702(tx) => Self::Eip7702(Cow::Borrowed(tx)),
#[cfg(feature = "optimism")]
super::Transaction::Deposit(tx) => Self::Deposit(TxDeposit::from(tx)),
}
}
}
impl<'a> From<Transaction<'a>> for super::Transaction {
fn from(value: Transaction<'a>) -> Self {
match value {
Transaction::Legacy(tx) => Self::Legacy(tx.into()),
Transaction::Eip2930(tx) => Self::Eip2930(tx.into()),
Transaction::Eip1559(tx) => Self::Eip1559(tx.into()),
Transaction::Eip4844(tx) => Self::Eip4844(tx.into_owned()),
Transaction::Eip7702(tx) => Self::Eip7702(tx.into_owned()),
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => Self::Deposit(tx.into()),
}
}
}
impl<'a> SerializeAs<super::Transaction> for Transaction<'a> {
fn serialize_as<S>(source: &super::Transaction, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
Transaction::from(source).serialize(serializer)
}
}
impl<'de> DeserializeAs<'de, super::Transaction> for Transaction<'de> {
fn deserialize_as<D>(deserializer: D) -> Result<super::Transaction, D::Error>
where
D: Deserializer<'de>,
{
Transaction::deserialize(deserializer).map(Into::into)
}
}
/// Bincode-compatible [`super::TransactionSigned`] serde implementation.
///
/// Intended to use with the [`serde_with::serde_as`] macro in the following way:
/// ```rust
/// use reth_primitives_traits::{serde_bincode_compat, TransactionSigned};
/// use serde::{Deserialize, Serialize};
/// use serde_with::serde_as;
///
/// #[serde_as]
/// #[derive(Serialize, Deserialize)]
/// struct Data {
/// #[serde_as(as = "serde_bincode_compat::transaction::TransactionSigned")]
/// transaction: TransactionSigned,
/// }
/// ```
#[derive(Debug, Serialize, Deserialize)]
pub struct TransactionSigned<'a> {
hash: TxHash,
signature: Signature,
transaction: Transaction<'a>,
}
impl<'a> From<&'a super::TransactionSigned> for TransactionSigned<'a> {
fn from(value: &'a super::TransactionSigned) -> Self {
Self {
hash: value.hash,
signature: value.signature,
transaction: Transaction::from(&value.transaction),
}
}
}
impl<'a> From<TransactionSigned<'a>> for super::TransactionSigned {
fn from(value: TransactionSigned<'a>) -> Self {
Self {
hash: value.hash,
signature: value.signature,
transaction: value.transaction.into(),
}
}
}
impl<'a> SerializeAs<super::TransactionSigned> for TransactionSigned<'a> {
fn serialize_as<S>(
source: &super::TransactionSigned,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
TransactionSigned::from(source).serialize(serializer)
}
}
impl<'de> DeserializeAs<'de, super::TransactionSigned> for TransactionSigned<'de> {
fn deserialize_as<D>(deserializer: D) -> Result<super::TransactionSigned, D::Error>
where
D: Deserializer<'de>,
{
TransactionSigned::deserialize(deserializer).map(Into::into)
}
}
#[cfg(test)]
mod tests {
use super::super::{serde_bincode_compat, Transaction, TransactionSigned};
use arbitrary::Arbitrary;
use rand::Rng;
use reth_testing_utils::generators;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
#[test]
fn test_transaction_bincode_roundtrip() {
#[serde_as]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Data {
#[serde_as(as = "serde_bincode_compat::Transaction")]
transaction: Transaction,
}
let mut bytes = [0u8; 1024];
generators::rng().fill(bytes.as_mut_slice());
let data = Data {
transaction: Transaction::arbitrary(&mut arbitrary::Unstructured::new(&bytes))
.unwrap(),
};
let encoded = bincode::serialize(&data).unwrap();
let decoded: Data = bincode::deserialize(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_transaction_signed_bincode_roundtrip() {
#[serde_as]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Data {
#[serde_as(as = "serde_bincode_compat::TransactionSigned")]
transaction: TransactionSigned,
}
let mut bytes = [0u8; 1024];
generators::rng().fill(bytes.as_mut_slice());
let data = Data {
transaction: TransactionSigned::arbitrary(&mut arbitrary::Unstructured::new(
&bytes,
))
.unwrap(),
};
let encoded = bincode::serialize(&data).unwrap();
let decoded: Data = bincode::deserialize(&encoded).unwrap();
assert_eq!(decoded, data);
}
}
}

View File

@ -23,7 +23,7 @@ zstd = { workspace = true, features = ["experimental", "zdict_builder"] }
lz4_flex = { version = "0.11", default-features = false }
memmap2 = "0.9.4"
bincode = "1.3"
bincode.workspace = true
serde = { workspace = true, features = ["derive"] }
tracing.workspace = true
anyhow = "1.0"