diff --git a/crates/consensus/Cargo.toml b/crates/consensus/Cargo.toml index 1a705bd04..b8aeb7631 100644 --- a/crates/consensus/Cargo.toml +++ b/crates/consensus/Cargo.toml @@ -26,6 +26,7 @@ arbitrary = { workspace = true, features = ["derive"], optional = true } # serde alloy-serde = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } +serde_with = { version = "3.9", optional = true } # misc spin.workspace = true @@ -35,8 +36,10 @@ derive_more = { workspace = true, features = ["display"] } alloy-primitives = { workspace = true, features = ["rand"] } alloy-signer.workspace = true arbitrary = { workspace = true, features = ["derive"] } -tokio = { workspace = true, features = ["macros"] } +bincode = "1.3" +rand.workspace = true serde_json.workspace = true +tokio = { workspace = true, features = ["macros"] } [features] default = ["std"] @@ -45,3 +48,4 @@ k256 = ["alloy-primitives/k256", "alloy-consensus/k256"] kzg = ["alloy-eips/kzg", "alloy-consensus/kzg", "std"] arbitrary = ["std", "dep:arbitrary", "alloy-consensus/arbitrary", "alloy-eips/arbitrary", "alloy-primitives/rand"] serde = ["dep:serde", "dep:alloy-serde", "alloy-primitives/serde", "alloy-consensus/serde", "alloy-eips/serde"] +serde-bincode-compat = ["serde_with"] diff --git a/crates/consensus/src/lib.rs b/crates/consensus/src/lib.rs index 9e0da8fda..16259f37f 100644 --- a/crates/consensus/src/lib.rs +++ b/crates/consensus/src/lib.rs @@ -24,3 +24,15 @@ pub use hardforks::Hardforks; mod block; pub use block::OpBlock; + +/// Bincode-compatible serde implementations for consensus types. +/// +/// `bincode` crate doesn't work well with optionally serializable serde fields, but some of the +/// consensus types require optional serialization for RPC compatibility. This module makes so that +/// all fields are serialized. +/// +/// Read more: +#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))] +pub mod serde_bincode_compat { + pub use super::transaction::serde_bincode_compat::TxDeposit; +} diff --git a/crates/consensus/src/transaction/deposit.rs b/crates/consensus/src/transaction/deposit.rs index 01a7699b3..0041e747b 100644 --- a/crates/consensus/src/transaction/deposit.rs +++ b/crates/consensus/src/transaction/deposit.rs @@ -356,3 +356,120 @@ mod tests { assert!(total_len > len_without_header); } } + +/// Bincode-compatible [`TxDeposit`] serde implementation. +#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))] +pub(super) mod serde_bincode_compat { + use alloc::borrow::Cow; + use alloy_primitives::{Address, Bytes, TxKind, B256, U256}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use serde_with::{DeserializeAs, SerializeAs}; + + /// Bincode-compatible [`super::TxDeposit`] serde implementation. + /// + /// Intended to use with the [`serde_with::serde_as`] macro in the following way: + /// ```rust + /// use op_alloy_consensus::{serde_bincode_compat, TxDeposit}; + /// use serde::{Deserialize, Serialize}; + /// use serde_with::serde_as; + /// + /// #[serde_as] + /// #[derive(Serialize, Deserialize)] + /// struct Data { + /// #[serde_as(as = "serde_bincode_compat::TxDeposit")] + /// transaction: TxDeposit, + /// } + /// ``` + #[derive(Debug, Serialize, Deserialize)] + pub struct TxDeposit<'a> { + source_hash: B256, + from: Address, + #[serde(default)] + to: TxKind, + #[serde(default)] + mint: Option, + value: U256, + gas_limit: u64, + is_system_transaction: bool, + input: Cow<'a, Bytes>, + } + + impl<'a> From<&'a super::TxDeposit> for TxDeposit<'a> { + fn from(value: &'a super::TxDeposit) -> Self { + Self { + source_hash: value.source_hash, + from: value.from, + to: value.to, + mint: value.mint, + value: value.value, + gas_limit: value.gas_limit, + is_system_transaction: value.is_system_transaction, + input: Cow::Borrowed(&value.input), + } + } + } + + impl<'a> From> for super::TxDeposit { + fn from(value: TxDeposit<'a>) -> Self { + Self { + source_hash: value.source_hash, + from: value.from, + to: value.to, + mint: value.mint, + value: value.value, + gas_limit: value.gas_limit, + is_system_transaction: value.is_system_transaction, + input: value.input.into_owned(), + } + } + } + + impl<'a> SerializeAs for TxDeposit<'a> { + fn serialize_as(source: &super::TxDeposit, serializer: S) -> Result + where + S: Serializer, + { + TxDeposit::from(source).serialize(serializer) + } + } + + impl<'de> DeserializeAs<'de, super::TxDeposit> for TxDeposit<'de> { + fn deserialize_as(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + TxDeposit::deserialize(deserializer).map(Into::into) + } + } + + #[cfg(test)] + mod tests { + use arbitrary::Arbitrary; + use rand::Rng; + use serde::{Deserialize, Serialize}; + use serde_with::serde_as; + + use super::super::{serde_bincode_compat, TxDeposit}; + + #[test] + fn test_tx_deposit_bincode_roundtrip() { + #[serde_as] + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct Data { + #[serde_as(as = "serde_bincode_compat::TxDeposit")] + transaction: TxDeposit, + } + + let mut bytes = [0u8; 1024]; + rand::thread_rng().fill(bytes.as_mut_slice()); + let data = Data { + transaction: TxDeposit::arbitrary(&mut arbitrary::Unstructured::new(&bytes)) + .unwrap(), + }; + + let encoded = bincode::serialize(&data).unwrap(); + let decoded: Data = bincode::deserialize(&encoded).unwrap(); + assert_eq!(decoded, data); + } + } +} diff --git a/crates/consensus/src/transaction/mod.rs b/crates/consensus/src/transaction/mod.rs index acbe394a3..2b3816f44 100644 --- a/crates/consensus/src/transaction/mod.rs +++ b/crates/consensus/src/transaction/mod.rs @@ -12,3 +12,9 @@ pub use source::{ DepositSourceDomain, DepositSourceDomainIdentifier, L1InfoDepositSource, UpgradeDepositSource, UserDepositSource, }; + +/// Bincode-compatible serde implementations for transaction types. +#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))] +pub(super) mod serde_bincode_compat { + pub use super::deposit::serde_bincode_compat::TxDeposit; +}