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

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