diff --git a/Cargo.lock b/Cargo.lock index bc2207865025..5657b04643d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8592,7 +8592,6 @@ dependencies = [ "rstest", "secp256k1", "serde", - "test-fuzz", ] [[package]] diff --git a/crates/optimism/primitives/Cargo.toml b/crates/optimism/primitives/Cargo.toml index 530099c49ffd..c3bd68deffe6 100644 --- a/crates/optimism/primitives/Cargo.toml +++ b/crates/optimism/primitives/Cargo.toml @@ -48,7 +48,6 @@ reth-codecs = { workspace = true, features = ["test-utils", "op"] } rstest.workspace = true arbitrary.workspace = true proptest.workspace = true -test-fuzz.workspace = true [features] default = ["std"] diff --git a/crates/optimism/primitives/src/receipt.rs b/crates/optimism/primitives/src/receipt.rs index 5b66087e7759..1c9ca442497c 100644 --- a/crates/optimism/primitives/src/receipt.rs +++ b/crates/optimism/primitives/src/receipt.rs @@ -1,65 +1,72 @@ -use alloc::vec::Vec; use alloy_consensus::{ - Eip2718EncodableReceipt, Eip658Value, ReceiptWithBloom, RlpDecodableReceipt, + Eip2718EncodableReceipt, Eip658Value, Receipt, ReceiptWithBloom, RlpDecodableReceipt, RlpEncodableReceipt, TxReceipt, Typed2718, }; use alloy_primitives::{Bloom, Log}; -use alloy_rlp::{BufMut, Decodable, Encodable, Header}; -use op_alloy_consensus::OpTxType; +use alloy_rlp::{BufMut, Decodable, Header}; +use op_alloy_consensus::{OpDepositReceipt, OpTxType}; use reth_primitives_traits::InMemorySize; /// Typed ethereum transaction receipt. /// Receipt containing result of transaction execution. -#[derive(Clone, Debug, PartialEq, Eq, Default)] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "reth-codec", derive(reth_codecs::CompactZstd), reth_codecs::add_arbitrary_tests, reth_zstd( - compressor = reth_zstd_compressors::RECEIPT_COMPRESSOR, - decompressor = reth_zstd_compressors::RECEIPT_DECOMPRESSOR -))] -pub struct OpReceipt { - /// Receipt type. - pub tx_type: OpTxType, - /// If transaction is executed successfully. - /// - /// This is the `statusCode` - pub success: bool, - /// Gas used - pub cumulative_gas_used: u64, - /// Log send from contracts. - pub logs: Vec, - /// Deposit nonce for Optimism deposit transactions - pub deposit_nonce: Option, - /// Deposit receipt version for Optimism deposit transactions - /// - /// - /// The deposit receipt version was introduced in Canyon to indicate an update to how - /// receipt hashes should be computed when set. The state transition process - /// ensures this is only set for post-Canyon deposit transactions. - pub deposit_receipt_version: Option, +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub enum OpReceipt { + /// Legacy receipt + Legacy(Receipt), + /// EIP-2930 receipt + Eip2930(Receipt), + /// EIP-1559 receipt + Eip1559(Receipt), + /// EIP-7702 receipt + Eip7702(Receipt), + /// Deposit receipt + Deposit(OpDepositReceipt), } impl OpReceipt { + /// Returns [`OpTxType`] of the receipt. + pub const fn tx_type(&self) -> OpTxType { + match self { + Self::Legacy(_) => OpTxType::Legacy, + Self::Eip2930(_) => OpTxType::Eip2930, + Self::Eip1559(_) => OpTxType::Eip1559, + Self::Eip7702(_) => OpTxType::Eip7702, + Self::Deposit(_) => OpTxType::Deposit, + } + } + + /// Returns inner [`Receipt`], + pub const fn as_receipt(&self) -> &Receipt { + match self { + Self::Legacy(receipt) | + Self::Eip2930(receipt) | + Self::Eip1559(receipt) | + Self::Eip7702(receipt) => receipt, + Self::Deposit(receipt) => &receipt.inner, + } + } + /// Returns length of RLP-encoded receipt fields with the given [`Bloom`] without an RLP header. pub fn rlp_encoded_fields_length(&self, bloom: &Bloom) -> usize { - self.success.length() + - self.cumulative_gas_used.length() + - bloom.length() + - self.logs.length() + - self.deposit_nonce.map(|n| n.length()).unwrap_or(0) + - self.deposit_receipt_version.map(|v| v.length()).unwrap_or(0) + match self { + Self::Legacy(receipt) | + Self::Eip2930(receipt) | + Self::Eip1559(receipt) | + Self::Eip7702(receipt) => receipt.rlp_encoded_fields_length_with_bloom(bloom), + Self::Deposit(receipt) => receipt.rlp_encoded_fields_length_with_bloom(bloom), + } } /// RLP-encodes receipt fields with the given [`Bloom`] without an RLP header. pub fn rlp_encode_fields(&self, bloom: &Bloom, out: &mut dyn BufMut) { - self.success.encode(out); - self.cumulative_gas_used.encode(out); - bloom.encode(out); - self.logs.encode(out); - if let Some(nonce) = self.deposit_nonce { - nonce.encode(out); - } - if let Some(version) = self.deposit_receipt_version { - version.encode(out); + match self { + Self::Legacy(receipt) | + Self::Eip2930(receipt) | + Self::Eip1559(receipt) | + Self::Eip7702(receipt) => receipt.rlp_encode_fields_with_bloom(bloom, out), + Self::Deposit(receipt) => receipt.rlp_encode_fields_with_bloom(bloom, out), } } @@ -74,55 +81,44 @@ impl OpReceipt { buf: &mut &[u8], tx_type: OpTxType, ) -> alloy_rlp::Result> { - let header = Header::decode(buf)?; - if !header.list { - return Err(alloy_rlp::Error::UnexpectedString); - } - - let remaining = buf.len(); - - let success = Decodable::decode(buf)?; - let cumulative_gas_used = Decodable::decode(buf)?; - let logs_bloom = Decodable::decode(buf)?; - let logs = Decodable::decode(buf)?; - - let (deposit_nonce, deposit_receipt_version) = if tx_type == OpTxType::Deposit { - let remaining = || header.payload_length - (remaining - buf.len()) > 0; - let deposit_nonce = remaining().then(|| Decodable::decode(buf)).transpose()?; - let deposit_receipt_version = - remaining().then(|| Decodable::decode(buf)).transpose()?; - - (deposit_nonce, deposit_receipt_version) - } else { - (None, None) - }; - - if buf.len() + header.payload_length != remaining { - return Err(alloy_rlp::Error::UnexpectedLength); + match tx_type { + OpTxType::Legacy => { + let ReceiptWithBloom { receipt, logs_bloom } = + RlpDecodableReceipt::rlp_decode_with_bloom(buf)?; + Ok(ReceiptWithBloom { receipt: Self::Legacy(receipt), logs_bloom }) + } + OpTxType::Eip2930 => { + let ReceiptWithBloom { receipt, logs_bloom } = + RlpDecodableReceipt::rlp_decode_with_bloom(buf)?; + Ok(ReceiptWithBloom { receipt: Self::Eip2930(receipt), logs_bloom }) + } + OpTxType::Eip1559 => { + let ReceiptWithBloom { receipt, logs_bloom } = + RlpDecodableReceipt::rlp_decode_with_bloom(buf)?; + Ok(ReceiptWithBloom { receipt: Self::Eip1559(receipt), logs_bloom }) + } + OpTxType::Eip7702 => { + let ReceiptWithBloom { receipt, logs_bloom } = + RlpDecodableReceipt::rlp_decode_with_bloom(buf)?; + Ok(ReceiptWithBloom { receipt: Self::Eip7702(receipt), logs_bloom }) + } + OpTxType::Deposit => { + let ReceiptWithBloom { receipt, logs_bloom } = + RlpDecodableReceipt::rlp_decode_with_bloom(buf)?; + Ok(ReceiptWithBloom { receipt: Self::Deposit(receipt), logs_bloom }) + } } - - Ok(ReceiptWithBloom { - receipt: Self { - cumulative_gas_used, - tx_type, - success, - logs, - deposit_nonce, - deposit_receipt_version, - }, - logs_bloom, - }) } } impl Eip2718EncodableReceipt for OpReceipt { fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize { - !self.tx_type.is_legacy() as usize + self.rlp_header_inner(bloom).length_with_payload() + !self.tx_type().is_legacy() as usize + self.rlp_header_inner(bloom).length_with_payload() } fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) { - if !self.tx_type.is_legacy() { - out.put_u8(self.tx_type as u8); + if !self.tx_type().is_legacy() { + out.put_u8(self.tx_type() as u8); } self.rlp_header_inner(bloom).encode(out); self.rlp_encode_fields(bloom, out); @@ -132,7 +128,7 @@ impl Eip2718EncodableReceipt for OpReceipt { impl RlpEncodableReceipt for OpReceipt { fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize { let mut len = self.eip2718_encoded_length_with_bloom(bloom); - if !self.tx_type.is_legacy() { + if !self.tx_type().is_legacy() { len += Header { list: false, payload_length: self.eip2718_encoded_length_with_bloom(bloom), @@ -144,7 +140,7 @@ impl RlpEncodableReceipt for OpReceipt { } fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) { - if !self.tx_type.is_legacy() { + if !self.tx_type().is_legacy() { Header { list: false, payload_length: self.eip2718_encoded_length_with_bloom(bloom) } .encode(out); } @@ -181,65 +177,123 @@ impl TxReceipt for OpReceipt { type Log = Log; fn status_or_post_state(&self) -> Eip658Value { - self.success.into() + self.as_receipt().status_or_post_state() } fn status(&self) -> bool { - self.success + self.as_receipt().status() } fn bloom(&self) -> Bloom { - alloy_primitives::logs_bloom(self.logs()) + self.as_receipt().bloom() } fn cumulative_gas_used(&self) -> u128 { - self.cumulative_gas_used as u128 + self.as_receipt().cumulative_gas_used() } fn logs(&self) -> &[Log] { - &self.logs + self.as_receipt().logs() } } impl Typed2718 for OpReceipt { fn ty(&self) -> u8 { - self.tx_type as u8 + self.tx_type().into() } } impl InMemorySize for OpReceipt { fn size(&self) -> usize { - self.tx_type.size() + - core::mem::size_of::() + - core::mem::size_of::() + - self.logs.capacity() * core::mem::size_of::() + self.as_receipt().size() } } impl reth_primitives_traits::Receipt for OpReceipt {} -#[cfg(feature = "arbitrary")] -impl arbitrary::Arbitrary<'_> for OpReceipt { - fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let tx_type = u.arbitrary()?; - - let (deposit_nonce, deposit_receipt_version) = if tx_type == OpTxType::Deposit { - let deposit_nonce: Option = u.arbitrary()?; - let deposit_receipt_version = - (deposit_nonce.is_some()).then(|| u.arbitrary()).transpose()?; - - (deposit_nonce, deposit_receipt_version) - } else { - (None, None) - }; - - Ok(Self { - logs: u.arbitrary()?, - success: u.arbitrary()?, - cumulative_gas_used: u.arbitrary()?, - tx_type, - deposit_nonce, - deposit_receipt_version, - }) +#[cfg(feature = "reth-codec")] +mod compact { + use super::*; + use alloc::borrow::Cow; + use reth_codecs::Compact; + + #[derive(reth_codecs::CompactZstd)] + #[reth_zstd( + compressor = reth_zstd_compressors::RECEIPT_COMPRESSOR, + decompressor = reth_zstd_compressors::RECEIPT_DECOMPRESSOR + )] + struct CompactOpReceipt<'a> { + tx_type: OpTxType, + success: bool, + cumulative_gas_used: u64, + logs: Cow<'a, Vec>, + deposit_nonce: Option, + deposit_receipt_version: Option, + } + + impl<'a> From<&'a OpReceipt> for CompactOpReceipt<'a> { + fn from(receipt: &'a OpReceipt) -> Self { + Self { + tx_type: receipt.tx_type(), + success: receipt.status(), + cumulative_gas_used: receipt.cumulative_gas_used() as u64, + logs: Cow::Borrowed(&receipt.as_receipt().logs), + deposit_nonce: if let OpReceipt::Deposit(receipt) = receipt { + receipt.deposit_nonce + } else { + None + }, + deposit_receipt_version: if let OpReceipt::Deposit(receipt) = receipt { + receipt.deposit_receipt_version + } else { + None + }, + } + } + } + + impl From> for OpReceipt { + fn from(receipt: CompactOpReceipt<'_>) -> Self { + let CompactOpReceipt { + tx_type, + success, + cumulative_gas_used, + logs, + deposit_nonce, + deposit_receipt_version, + } = receipt; + + let inner = Receipt { + status: success.into(), + cumulative_gas_used: cumulative_gas_used as u128, + logs: logs.into_owned(), + }; + + match tx_type { + OpTxType::Legacy => Self::Legacy(inner), + OpTxType::Eip2930 => Self::Eip2930(inner), + OpTxType::Eip1559 => Self::Eip1559(inner), + OpTxType::Eip7702 => Self::Eip7702(inner), + OpTxType::Deposit => Self::Deposit(OpDepositReceipt { + inner, + deposit_nonce, + deposit_receipt_version, + }), + } + } + } + + impl Compact for OpReceipt { + fn to_compact(&self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + CompactOpReceipt::from(self).to_compact(buf) + } + + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { + let (receipt, buf) = CompactOpReceipt::from_compact(buf, len); + (receipt.into(), buf) + } } } diff --git a/crates/primitives-traits/src/size.rs b/crates/primitives-traits/src/size.rs index a1978ff379e0..be19ae81282a 100644 --- a/crates/primitives-traits/src/size.rs +++ b/crates/primitives-traits/src/size.rs @@ -1,5 +1,6 @@ use alloy_consensus::{Header, TxEip1559, TxEip2930, TxEip4844, TxEip7702, TxLegacy, TxType}; use alloy_primitives::{PrimitiveSignature as Signature, TxHash}; +use revm_primitives::Log; /// Trait for calculating a heuristic for the in-memory size of a struct. #[auto_impl::auto_impl(&, Arc, Box)] @@ -49,6 +50,25 @@ impl_in_mem_size!(Header, TxLegacy, TxEip2930, TxEip1559, TxEip7702, TxEip4844); #[cfg(feature = "op")] impl_in_mem_size_size_of!(op_alloy_consensus::OpTxType); +impl InMemorySize for alloy_consensus::Receipt { + fn size(&self) -> usize { + let Self { status, cumulative_gas_used, logs } = self; + core::mem::size_of_val(status) + + core::mem::size_of_val(cumulative_gas_used) + + logs.capacity() * core::mem::size_of::() + } +} + +#[cfg(feature = "op")] +impl InMemorySize for op_alloy_consensus::OpDepositReceipt { + fn size(&self) -> usize { + let Self { inner, deposit_nonce, deposit_receipt_version } = self; + inner.size() + + core::mem::size_of_val(deposit_nonce) + + core::mem::size_of_val(deposit_receipt_version) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/primitives/src/receipt.rs b/crates/primitives/src/receipt.rs index b6e7949a3dc8..d4c15fe856aa 100644 --- a/crates/primitives/src/receipt.rs +++ b/crates/primitives/src/receipt.rs @@ -21,7 +21,6 @@ pub use reth_primitives_traits::receipt::gas_spent_by_transactions; Clone, Debug, PartialEq, Eq, Default, RlpEncodable, RlpDecodable, Serialize, Deserialize, )] #[cfg_attr(any(test, feature = "reth-codec"), derive(reth_codecs::CompactZstd))] -#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests)] #[cfg_attr(any(test, feature = "reth-codec"), reth_zstd( compressor = reth_zstd_compressors::RECEIPT_COMPRESSOR, decompressor = reth_zstd_compressors::RECEIPT_DECOMPRESSOR diff --git a/crates/storage/codecs/derive/src/compact/flags.rs b/crates/storage/codecs/derive/src/compact/flags.rs index 798c9ad53b45..622eba60b28b 100644 --- a/crates/storage/codecs/derive/src/compact/flags.rs +++ b/crates/storage/codecs/derive/src/compact/flags.rs @@ -126,7 +126,9 @@ fn build_struct_field_flags( let mut total_bits = 0; // Find out the adequate bit size for the length of each field, if applicable. - for (name, ftype, is_compact, _) in fields { + for field in fields { + let StructFieldDescriptor { name, ftype, is_compact, use_alt_impl: _, is_reference: _ } = + field; // This happens when dealing with a wrapper struct eg. Struct(pub U256). let name = if name.is_empty() { "placeholder" } else { name }; diff --git a/crates/storage/codecs/derive/src/compact/generator.rs b/crates/storage/codecs/derive/src/compact/generator.rs index a84913f59e81..26a1f10127f4 100644 --- a/crates/storage/codecs/derive/src/compact/generator.rs +++ b/crates/storage/codecs/derive/src/compact/generator.rs @@ -41,7 +41,15 @@ pub fn generate_from_to( } }; - let fn_from_compact = if has_lifetime { + let has_ref_fields = fields.iter().any(|field| { + if let FieldTypes::StructField(field) = field { + field.is_reference + } else { + false + } + }); + + let fn_from_compact = if has_ref_fields { quote! { unimplemented!("from_compact not supported with ref structs") } } else { quote! { @@ -100,7 +108,7 @@ fn generate_from_compact( ) -> TokenStream2 { let mut lines = vec![]; let mut known_types = - vec!["B256", "Address", "Bloom", "Vec", "TxHash", "BlockHash", "FixedBytes"]; + vec!["B256", "Address", "Bloom", "Vec", "TxHash", "BlockHash", "FixedBytes", "Cow"]; // Only types without `Bytes` should be added here. It's currently manually added, since // it's hard to figure out with derive_macro which types have Bytes fields. @@ -132,8 +140,8 @@ fn generate_from_compact( }); } else { let fields = fields.iter().filter_map(|field| { - if let FieldTypes::StructField((name, _, _, _)) = field { - let ident = format_ident!("{name}"); + if let FieldTypes::StructField(field) = field { + let ident = format_ident!("{}", field.name); return Some(quote! { #ident: #ident, }) diff --git a/crates/storage/codecs/derive/src/compact/mod.rs b/crates/storage/codecs/derive/src/compact/mod.rs index 4010e106f5a4..e7906bbdb506 100644 --- a/crates/storage/codecs/derive/src/compact/mod.rs +++ b/crates/storage/codecs/derive/src/compact/mod.rs @@ -17,10 +17,6 @@ use structs::*; use crate::ZstdConfig; -// Helper Alias type -type IsCompact = bool; -// Helper Alias type -type FieldName = String; // Helper Alias type type FieldType = String; /// `Compact` has alternative functions that can be used as a workaround for type @@ -30,7 +26,14 @@ type FieldType = String; /// require the len of the element, while the latter one does. type UseAlternative = bool; // Helper Alias type -type StructFieldDescriptor = (FieldName, FieldType, IsCompact, UseAlternative); +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct StructFieldDescriptor { + name: String, + ftype: String, + is_compact: bool, + use_alt_impl: bool, + is_reference: bool, +} // Helper Alias type type FieldList = Vec; @@ -150,12 +153,13 @@ fn load_field_from_segments( attr.path().segments.iter().any(|path| path.ident == "maybe_zero") }); - fields.push(FieldTypes::StructField(( - field.ident.as_ref().map(|i| i.to_string()).unwrap_or_default(), + fields.push(FieldTypes::StructField(StructFieldDescriptor { + name: field.ident.as_ref().map(|i| i.to_string()).unwrap_or_default(), ftype, - should_compact, + is_compact: should_compact, use_alt_impl, - ))); + is_reference: matches!(field.ty, syn::Type::Reference(_)), + })); } } } diff --git a/crates/storage/codecs/derive/src/compact/structs.rs b/crates/storage/codecs/derive/src/compact/structs.rs index 07d7a3803ade..f8ebda33499e 100644 --- a/crates/storage/codecs/derive/src/compact/structs.rs +++ b/crates/storage/codecs/derive/src/compact/structs.rs @@ -44,7 +44,8 @@ impl<'a> StructHandler<'a> { /// Generates `to_compact` code for a struct field. fn to(&mut self, field_descriptor: &StructFieldDescriptor) { - let (name, ftype, is_compact, use_alt_impl) = field_descriptor; + let StructFieldDescriptor { name, ftype, is_compact, use_alt_impl, is_reference: _ } = + field_descriptor; let to_compact_ident = if *use_alt_impl { format_ident!("specialized_to_compact") @@ -97,7 +98,7 @@ impl<'a> StructHandler<'a> { /// Generates `from_compact` code for a struct field. fn from(&mut self, field_descriptor: &StructFieldDescriptor, known_types: &[&str]) { - let (name, ftype, is_compact, use_alt_impl) = field_descriptor; + let StructFieldDescriptor { name, ftype, is_compact, use_alt_impl, .. } = field_descriptor; let (name, len) = if name.is_empty() { self.is_wrapper = true; diff --git a/crates/storage/codecs/src/lib.rs b/crates/storage/codecs/src/lib.rs index 8c6ba5e4c766..cf18e548bd5d 100644 --- a/crates/storage/codecs/src/lib.rs +++ b/crates/storage/codecs/src/lib.rs @@ -25,7 +25,10 @@ use serde as _; use alloy_primitives::{Address, Bloom, Bytes, FixedBytes, U256}; use bytes::{Buf, BufMut}; -use alloc::vec::Vec; +use alloc::{ + borrow::{Cow, ToOwned}, + vec::Vec, +}; #[cfg(feature = "test-utils")] pub mod alloy; @@ -343,6 +346,32 @@ where } } +impl> Compact for Cow<'_, T> { + fn to_compact(&self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + self.as_ref().to_compact(buf) + } + + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { + let (element, buf) = T::from_compact(buf, len); + (Cow::Owned(element), buf) + } + + fn specialized_to_compact(&self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + self.as_ref().specialized_to_compact(buf) + } + + fn specialized_from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { + let (element, buf) = T::specialized_from_compact(buf, len); + (Cow::Owned(element), buf) + } +} + impl Compact for U256 { #[inline] fn to_compact(&self, buf: &mut B) -> usize