From 0200ad6ee06e93cd194c35fdce19ae387a640052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Mon, 24 Mar 2025 14:05:16 +0100 Subject: [PATCH] feat(storage): Implement `Compact` for `OpTxEnvelope` from `op_alloy` using blanket impl (#15230) --- .../primitives/src/transaction/signed.rs | 34 ++++++ .../codecs/src/alloy/transaction/ethereum.rs | 63 ++++++++++- .../codecs/src/alloy/transaction/optimism.rs | 104 ++++++++++++++++-- 3 files changed, 189 insertions(+), 12 deletions(-) diff --git a/crates/optimism/primitives/src/transaction/signed.rs b/crates/optimism/primitives/src/transaction/signed.rs index 726f3432f7..961113abf1 100644 --- a/crates/optimism/primitives/src/transaction/signed.rs +++ b/crates/optimism/primitives/src/transaction/signed.rs @@ -778,3 +778,37 @@ pub mod serde_bincode_compat { } } } + +#[cfg(test)] +mod tests { + use super::*; + use proptest::proptest; + use proptest_arbitrary_interop::arb; + use reth_codecs::Compact; + + proptest! { + #[test] + fn test_roundtrip_compact_encode_envelope(reth_tx in arb::()) { + let mut expected_buf = Vec::::new(); + let expected_len = reth_tx.to_compact(&mut expected_buf); + + let mut actual_but = Vec::::new(); + let alloy_tx = OpTxEnvelope::from(reth_tx); + let actual_len = alloy_tx.to_compact(&mut actual_but); + + assert_eq!(actual_but, expected_buf); + assert_eq!(actual_len, expected_len); + } + + #[test] + fn test_roundtrip_compact_decode_envelope(reth_tx in arb::()) { + let mut buf = Vec::::new(); + let len = reth_tx.to_compact(&mut buf); + + let (actual_tx, _) = OpTxEnvelope::from_compact(&buf, len); + let expected_tx = OpTxEnvelope::from(reth_tx); + + assert_eq!(actual_tx, expected_tx); + } + } +} diff --git a/crates/storage/codecs/src/alloy/transaction/ethereum.rs b/crates/storage/codecs/src/alloy/transaction/ethereum.rs index 265e289c76..0c7901433c 100644 --- a/crates/storage/codecs/src/alloy/transaction/ethereum.rs +++ b/crates/storage/codecs/src/alloy/transaction/ethereum.rs @@ -14,7 +14,7 @@ use bytes::{Buf, BufMut}; /// serialized separately. /// /// See [`ToTxCompact::to_tx_compact`]. -trait ToTxCompact { +pub(super) trait ToTxCompact { /// Serializes inner transaction using [`Compact`] encoding. Writes the result into `buf`. /// /// The written bytes do not contain signature and transaction type. This information be needs @@ -30,14 +30,20 @@ trait ToTxCompact { /// separately. /// /// See [`FromTxCompact::from_tx_compact`]. -trait FromTxCompact { +pub(super) trait FromTxCompact { + type TxType; + /// Deserializes inner transaction using [`Compact`] encoding. The concrete type is determined /// by `tx_type`. The `signature` is added to create typed and signed transaction. /// /// Returns a tuple of 2 elements. The first element is the deserialized value and the second /// is a byte slice created from `buf` with a starting position advanced by the exact amount /// of bytes consumed for this process. - fn from_tx_compact(buf: &[u8], tx_type: TxType, signature: PrimitiveSignature) -> (Self, &[u8]) + fn from_tx_compact( + buf: &[u8], + tx_type: Self::TxType, + signature: PrimitiveSignature, + ) -> (Self, &[u8]) where Self: Sized; } @@ -55,6 +61,8 @@ impl ToTxCompact for EthereumTxEnvelope } impl FromTxCompact for EthereumTxEnvelope { + type TxType = TxType; + fn from_tx_compact( buf: &[u8], tx_type: TxType, @@ -90,9 +98,39 @@ impl FromTxCompact for EthereumTxEnvelope Compact +pub(super) trait Envelope: FromTxCompact { + fn signature(&self) -> &PrimitiveSignature; + fn tx_type(&self) -> Self::TxType; +} + +impl Envelope for EthereumTxEnvelope { + fn signature(&self) -> &PrimitiveSignature { + Self::signature(self) + } + + fn tx_type(&self) -> Self::TxType { + Self::tx_type(self) + } +} + +pub(super) trait CompactEnvelope: Sized { + /// Takes a buffer which can be written to. *Ideally*, it returns the length written to. + fn to_compact(&self, buf: &mut B) -> usize + where + B: BufMut + AsMut<[u8]>; + + /// Takes a buffer which can be read from. Returns the object and `buf` with its internal cursor + /// advanced (eg.`.advance(len)`). + /// + /// `len` can either be the `buf` remaining length, or the length of the compacted type. + /// + /// It will panic, if `len` is smaller than `buf.len()`. + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]); +} + +impl CompactEnvelope for T { fn to_compact(&self, buf: &mut B) -> usize where B: BufMut + AsMut<[u8]>, @@ -147,7 +185,7 @@ impl Compact let zstd_bit = flags >> 3; let (signature, buf) = PrimitiveSignature::from_compact(buf, sig_bit); - let (tx_type, buf) = TxType::from_compact(buf, tx_bits); + let (tx_type, buf) = T::TxType::from_compact(buf, tx_bits); let (transaction, buf) = if zstd_bit != 0 { #[cfg(feature = "std")] @@ -177,3 +215,18 @@ impl Compact (transaction, buf) } } + +impl Compact + for EthereumTxEnvelope +{ + fn to_compact(&self, buf: &mut B) -> usize + where + B: BufMut + AsMut<[u8]>, + { + ::to_compact(self, buf) + } + + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { + ::from_compact(buf, len) + } +} diff --git a/crates/storage/codecs/src/alloy/transaction/optimism.rs b/crates/storage/codecs/src/alloy/transaction/optimism.rs index 7b4cf57055..8f67fe4535 100644 --- a/crates/storage/codecs/src/alloy/transaction/optimism.rs +++ b/crates/storage/codecs/src/alloy/transaction/optimism.rs @@ -1,12 +1,21 @@ //! Compact implementation for [`AlloyTxDeposit`] -use alloy_consensus::constants::EIP7702_TX_TYPE_ID; -use crate::Compact; -use alloy_primitives::{Address, Bytes, TxKind, B256, U256}; -use op_alloy_consensus::{OpTxType, OpTypedTransaction, TxDeposit as AlloyTxDeposit}; +use crate::{ + alloy::transaction::ethereum::{CompactEnvelope, Envelope, FromTxCompact, ToTxCompact}, + generate_tests, + txtype::{ + COMPACT_EXTENDED_IDENTIFIER_FLAG, COMPACT_IDENTIFIER_EIP1559, COMPACT_IDENTIFIER_EIP2930, + COMPACT_IDENTIFIER_LEGACY, + }, + Compact, +}; +use alloy_consensus::{ + constants::EIP7702_TX_TYPE_ID, Signed, TxEip1559, TxEip2930, TxEip7702, TxLegacy, +}; +use alloy_primitives::{Address, Bytes, PrimitiveSignature, Sealed, TxKind, B256, U256}; +use bytes::BufMut; +use op_alloy_consensus::{OpTxEnvelope, OpTxType, OpTypedTransaction, TxDeposit as AlloyTxDeposit}; use reth_codecs_derive::add_arbitrary_tests; -use crate::txtype::{COMPACT_EXTENDED_IDENTIFIER_FLAG, COMPACT_IDENTIFIER_EIP1559, COMPACT_IDENTIFIER_EIP2930, COMPACT_IDENTIFIER_LEGACY}; -use crate::generate_tests; /// Deposit transactions, also known as deposits are initiated on L1, and executed on L2. /// @@ -69,7 +78,6 @@ impl Compact for AlloyTxDeposit { } } - impl crate::Compact for OpTxType { fn to_compact(&self, buf: &mut B) -> usize where @@ -160,4 +168,86 @@ impl Compact for OpTypedTransaction { } } +impl ToTxCompact for OpTxEnvelope { + fn to_tx_compact(&self, buf: &mut (impl BufMut + AsMut<[u8]>)) { + match self { + Self::Legacy(tx) => tx.tx().to_compact(buf), + Self::Eip2930(tx) => tx.tx().to_compact(buf), + Self::Eip1559(tx) => tx.tx().to_compact(buf), + Self::Eip7702(tx) => tx.tx().to_compact(buf), + Self::Deposit(tx) => tx.to_compact(buf), + }; + } +} + +impl FromTxCompact for OpTxEnvelope { + type TxType = OpTxType; + + fn from_tx_compact( + buf: &[u8], + tx_type: OpTxType, + signature: PrimitiveSignature, + ) -> (Self, &[u8]) { + match tx_type { + OpTxType::Legacy => { + let (tx, buf) = TxLegacy::from_compact(buf, buf.len()); + let tx = Signed::new_unhashed(tx, signature); + (Self::Legacy(tx), buf) + } + OpTxType::Eip2930 => { + let (tx, buf) = TxEip2930::from_compact(buf, buf.len()); + let tx = Signed::new_unhashed(tx, signature); + (Self::Eip2930(tx), buf) + } + OpTxType::Eip1559 => { + let (tx, buf) = TxEip1559::from_compact(buf, buf.len()); + let tx = Signed::new_unhashed(tx, signature); + (Self::Eip1559(tx), buf) + } + OpTxType::Eip7702 => { + let (tx, buf) = TxEip7702::from_compact(buf, buf.len()); + let tx = Signed::new_unhashed(tx, signature); + (Self::Eip7702(tx), buf) + } + OpTxType::Deposit => { + let (tx, buf) = op_alloy_consensus::TxDeposit::from_compact(buf, buf.len()); + let tx = Sealed::new(tx); + (Self::Deposit(tx), buf) + } + } + } +} + +const DEPOSIT_SIGNATURE: PrimitiveSignature = + PrimitiveSignature::new(U256::ZERO, U256::ZERO, false); + +impl Envelope for OpTxEnvelope { + fn signature(&self) -> &PrimitiveSignature { + match self { + Self::Legacy(tx) => tx.signature(), + Self::Eip2930(tx) => tx.signature(), + Self::Eip1559(tx) => tx.signature(), + Self::Eip7702(tx) => tx.signature(), + Self::Deposit(_) => &DEPOSIT_SIGNATURE, + } + } + + fn tx_type(&self) -> Self::TxType { + Self::tx_type(self) + } +} + +impl Compact for OpTxEnvelope { + fn to_compact(&self, buf: &mut B) -> usize + where + B: BufMut + AsMut<[u8]>, + { + CompactEnvelope::to_compact(self, buf) + } + + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { + CompactEnvelope::from_compact(buf, len) + } +} + generate_tests!(#[crate, compact] OpTypedTransaction, OpTypedTransactionTests);